diff --git a/Core.ApplicationServices/AdviceService.cs b/Core.ApplicationServices/AdviceService.cs index e3832e05b6..742481fcfc 100644 --- a/Core.ApplicationServices/AdviceService.cs +++ b/Core.ApplicationServices/AdviceService.cs @@ -46,7 +46,7 @@ public class AdviceService: IAdviceService public IGenericRepository _itSystemUsageRepository { get; set; } public AdviceService() {} - public bool sendAdvice(int id){ + public bool SendAdvice(int id){ var advice = _adviceRepository.AsQueryable().FirstOrDefault(a => a.Id == id); @@ -172,7 +172,6 @@ public bool sendAdvice(int id){ } catch (Exception e) { - //todo log exception this.Logger?.Error(e, "Error in Advis service"); return false; } @@ -304,7 +303,6 @@ public bool sendAdvice(int id){ } catch (Exception e) { - //todo log exception this.Logger?.Error(e, "Error in Advis service"); return false; } diff --git a/Core.ApplicationServices/ApplicationServiceModule.cs b/Core.ApplicationServices/ApplicationServiceModule.cs index ccd676cd52..629c45937b 100644 --- a/Core.ApplicationServices/ApplicationServiceModule.cs +++ b/Core.ApplicationServices/ApplicationServiceModule.cs @@ -1,9 +1,4 @@ using Ninject.Modules; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Core.ApplicationServices { diff --git a/Core.ApplicationServices/Authentication/AuthenticationContext.cs b/Core.ApplicationServices/Authentication/AuthenticationContext.cs new file mode 100644 index 0000000000..096920a164 --- /dev/null +++ b/Core.ApplicationServices/Authentication/AuthenticationContext.cs @@ -0,0 +1,22 @@ +namespace Core.ApplicationServices.Authentication +{ + public class AuthenticationContext : IAuthenticationContext + { + public AuthenticationMethod Method { get; } + public int? UserId { get; } + public int? ActiveOrganizationId { get; } + public bool HasApiAccess { get; } + + public AuthenticationContext( + AuthenticationMethod method, + bool hasApiAccess, + int? userId = null, + int? activeOrganizationId = null) + { + Method = method; + UserId = userId; + ActiveOrganizationId = activeOrganizationId; + HasApiAccess = hasApiAccess; + } + } +} \ No newline at end of file diff --git a/Core.ApplicationServices/Authentication/AuthenticationMethod.cs b/Core.ApplicationServices/Authentication/AuthenticationMethod.cs new file mode 100644 index 0000000000..30b8283db8 --- /dev/null +++ b/Core.ApplicationServices/Authentication/AuthenticationMethod.cs @@ -0,0 +1,9 @@ +namespace Core.ApplicationServices.Authentication +{ + public enum AuthenticationMethod + { + Anonymous, + KitosToken, + Forms + } +} \ No newline at end of file diff --git a/Core.ApplicationServices/Authentication/IAuthenticationContext.cs b/Core.ApplicationServices/Authentication/IAuthenticationContext.cs new file mode 100644 index 0000000000..5bbba2b499 --- /dev/null +++ b/Core.ApplicationServices/Authentication/IAuthenticationContext.cs @@ -0,0 +1,11 @@ +namespace Core.ApplicationServices.Authentication +{ + public interface IAuthenticationContext + { + AuthenticationMethod Method { get; } + int? UserId { get; } + int? ActiveOrganizationId { get; } + + bool HasApiAccess { get; } + } +} diff --git a/Core.ApplicationServices/Authentication/IAuthenticationContextFactory.cs b/Core.ApplicationServices/Authentication/IAuthenticationContextFactory.cs new file mode 100644 index 0000000000..80c192ad35 --- /dev/null +++ b/Core.ApplicationServices/Authentication/IAuthenticationContextFactory.cs @@ -0,0 +1,7 @@ +namespace Core.ApplicationServices.Authentication +{ + public interface IAuthenticationContextFactory + { + IAuthenticationContext Create(); + } +} diff --git a/Core.ApplicationServices/AuthenticationService.cs b/Core.ApplicationServices/AuthenticationService.cs index 1b1de8b0f3..c11f9accec 100644 --- a/Core.ApplicationServices/AuthenticationService.cs +++ b/Core.ApplicationServices/AuthenticationService.cs @@ -14,7 +14,7 @@ public class AuthenticationService : IAuthenticationService { private readonly IGenericRepository _userRepository; - public readonly IFeatureChecker _featureChecker; + private readonly IFeatureChecker _featureChecker; public AuthenticationService(IGenericRepository userRepository, IFeatureChecker featureChecker) { @@ -28,23 +28,6 @@ public bool IsGlobalAdmin(int userId) return user.IsGlobalAdmin; } - /// - /// Checks if the user is local admin in a respective organization. - /// - /// - /// - /// - public bool IsLocalAdmin(int userId, int organizationId) - { - var user = _userRepository.AsQueryable() - .SingleOrDefault(x => x.Id == userId && - x.OrganizationRights.Any( - right => right.Role == OrganizationRole.LocalAdmin && - right.OrganizationId == organizationId)); - - return user != null; - } - /// /// Checks if the user is local admin in the current organization. /// @@ -81,8 +64,14 @@ public bool HasReadAccessOutsideContext(int userId) /// Returns true if the user have read access to the given instance, else false. public bool HasReadAccess(int userId, IEntity entity) { - var user = _userRepository.AsQueryable().Single(x => x.Id == userId); - var loggedIntoOrganizationId = user.DefaultOrganizationId.Value; + var user = _userRepository.GetByKey(userId); + + var loggedIntoOrganizationId = user.DefaultOrganizationId.GetValueOrDefault(-1); + if (loggedIntoOrganizationId == -1) + { + return false; + } + // check if global admin if (user.IsGlobalAdmin) { @@ -136,7 +125,12 @@ public bool HasWriteAccess(int userId, IEntity entity) { var user = _userRepository.AsQueryable().Single(x => x.Id == userId); AssertUserIsNotNull(user); - var loggedIntoOrganizationId = user.DefaultOrganizationId.Value; + var loggedIntoOrganizationId = user.DefaultOrganizationId.GetValueOrDefault(-1); + + if (loggedIntoOrganizationId == -1) + { + return false; + } // check if global admin if (user.IsGlobalAdmin) @@ -144,16 +138,18 @@ public bool HasWriteAccess(int userId, IEntity entity) // global admin always have access return true; } - //check if user is readonly - if (user.IsReadOnly) { - return false; + + // check "Forretningsroller" for the entity + if (entity.HasUserWriteAccess(user)) + { + return true; } - //User has access if user created entity - //if (user.IsLocalAdmin && entity.ObjectOwnerId == user.Id) - //{ - // return true; - //} + // check ReadOnly + if (user.IsReadOnly) + { + return false; + } //Check if user is allowed to set accessmodifier to public var accessModifier = (entity as IHasAccessModifier)?.AccessModifier; @@ -175,6 +171,7 @@ public bool HasWriteAccess(int userId, IEntity entity) return false; } } + else if (!_featureChecker.CanExecute(user, Feature.CanSetAccessModifierToPublic)) { return false; @@ -212,18 +209,13 @@ public bool HasWriteAccess(int userId, IEntity entity) if (_featureChecker.CanExecute(user, Feature.CanModifyReports) && entity is IReportModule) return true; - // check if user has a write role on the target entity - if (entity.HasUserWriteAccess(user)) - return true; - // check if user is object owner - if (entity.ObjectOwnerId == user.Id) + if (entity.ObjectOwner != null && entity.ObjectOwner.Id == user.Id && (entity is IProjectModule || entity is ISystemModule || entity is ItContract || entity is IReportModule)) { // object owners have write access to their objects if they're within the context, // else they'll have to switch to the correct context and try again return true; - - } + } // User is a special case if (entity is User && (entity.Id == user.Id || _featureChecker.CanExecute(user, Feature.CanModifyUsers))) diff --git a/Core.ApplicationServices/Authorization/AuthorizationContextFactory.cs b/Core.ApplicationServices/Authorization/AuthorizationContextFactory.cs new file mode 100644 index 0000000000..d3728494e5 --- /dev/null +++ b/Core.ApplicationServices/Authorization/AuthorizationContextFactory.cs @@ -0,0 +1,12 @@ +namespace Core.ApplicationServices.Authorization +{ + public class AuthorizationContextFactory : IAuthorizationContextFactory + { + public IAuthorizationContext Create(IOrganizationalUserContext userContext) + { + return userContext is UnauthenticatedUserContext + ? new UnauthenticatedAuthorizationContext() + : (IAuthorizationContext) new OrganizationAuthorizationContext(userContext); + } + } +} \ No newline at end of file diff --git a/Core.ApplicationServices/Authorization/IAuthorizationContext.cs b/Core.ApplicationServices/Authorization/IAuthorizationContext.cs new file mode 100644 index 0000000000..4533cd4a38 --- /dev/null +++ b/Core.ApplicationServices/Authorization/IAuthorizationContext.cs @@ -0,0 +1,57 @@ +using Core.DomainModel; +using Core.DomainServices.Authorization; + +namespace Core.ApplicationServices.Authorization +{ + public interface IAuthorizationContext + { + /// + /// Determine the granularity of cross organization read access supported by the current authorization context + /// + /// + CrossOrganizationDataReadAccessLevel GetCrossOrganizationReadAccess(); + /// + /// Determines, at a high level, the depth of read-access which is allowed on objects within the target organization wrt. the active organization. + /// NOTE: Does not provide entity-level access rights. Just answers the question if ANY access at all can be granted. + /// + /// + /// + OrganizationDataReadAccessLevel GetOrganizationReadAccessLevel(int organizationId); + /// + /// Determines if read-access is allowed for the provided entity + /// + /// + /// + bool AllowReads(IEntity entity); + /// + /// Determines if create-access is allowed for the provided entity type + /// + /// + bool AllowCreate(); + /// + /// Determines if create-access is allowed for the provided entity type and with the representation passed in + /// + /// + /// + /// + bool AllowCreate(IEntity entity); + /// + /// Determines if update-access is allowed for the provided entity + /// + /// + /// + bool AllowModify(IEntity entity); + /// + /// Determines if delete-access is allowed for the provided entity + /// + /// + /// + bool AllowDelete(IEntity entity); + /// + /// Determines if write-access is allowed to entity's visibility control + /// + /// + /// + bool AllowEntityVisibilityControl(IEntity entity); + } +} diff --git a/Core.ApplicationServices/Authorization/IAuthorizationContextFactory.cs b/Core.ApplicationServices/Authorization/IAuthorizationContextFactory.cs new file mode 100644 index 0000000000..87fcaeb7a4 --- /dev/null +++ b/Core.ApplicationServices/Authorization/IAuthorizationContextFactory.cs @@ -0,0 +1,7 @@ +namespace Core.ApplicationServices.Authorization +{ + public interface IAuthorizationContextFactory + { + IAuthorizationContext Create(IOrganizationalUserContext userContext); + } +} \ No newline at end of file diff --git a/Core.ApplicationServices/Authorization/IOrganizationalUserContext.cs b/Core.ApplicationServices/Authorization/IOrganizationalUserContext.cs new file mode 100644 index 0000000000..09086308e7 --- /dev/null +++ b/Core.ApplicationServices/Authorization/IOrganizationalUserContext.cs @@ -0,0 +1,19 @@ +using Core.DomainModel; +using Core.DomainModel.Organization; + +namespace Core.ApplicationServices.Authorization +{ + public interface IOrganizationalUserContext + { + int ActiveOrganizationId { get; } + int UserId { get; } + bool IsActiveInOrganizationOfType(OrganizationCategory category); + bool HasRole(OrganizationRole role); + bool HasModuleLevelAccessTo(IEntity entity); + bool IsActiveInOrganization(int organizationId); + bool IsActiveInSameOrganizationAs(IEntity entity); + bool HasAssignedWriteAccess(IEntity entity); + bool HasOwnership(IEntity entity); + bool CanChangeVisibilityOf(IEntity entity); + } +} diff --git a/Core.ApplicationServices/Authorization/IUserContextFactory.cs b/Core.ApplicationServices/Authorization/IUserContextFactory.cs new file mode 100644 index 0000000000..842d1bf35c --- /dev/null +++ b/Core.ApplicationServices/Authorization/IUserContextFactory.cs @@ -0,0 +1,7 @@ +namespace Core.ApplicationServices.Authorization +{ + public interface IUserContextFactory + { + IOrganizationalUserContext Create(int userId, int organizationId); + } +} diff --git a/Core.ApplicationServices/Authorization/OrganizationAuthorizationContext.cs b/Core.ApplicationServices/Authorization/OrganizationAuthorizationContext.cs new file mode 100644 index 0000000000..f7281bce59 --- /dev/null +++ b/Core.ApplicationServices/Authorization/OrganizationAuthorizationContext.cs @@ -0,0 +1,244 @@ +using Core.DomainModel; +using Core.DomainModel.ItSystem; +using Core.DomainModel.Organization; +using Core.DomainServices.Authorization; + +namespace Core.ApplicationServices.Authorization +{ + public class OrganizationAuthorizationContext : IAuthorizationContext + { + private readonly IOrganizationalUserContext _activeUserContext; + + public OrganizationAuthorizationContext(IOrganizationalUserContext activeUserContext) + { + _activeUserContext = activeUserContext; + } + + public CrossOrganizationDataReadAccessLevel GetCrossOrganizationReadAccess() + { + if (IsGlobalAdmin()) + { + return CrossOrganizationDataReadAccessLevel.All; + } + + return IsUserInMunicipality() ? + CrossOrganizationDataReadAccessLevel.Public : + CrossOrganizationDataReadAccessLevel.None; + } + + public OrganizationDataReadAccessLevel GetOrganizationReadAccessLevel(int organizationId) + { + if (TargetOrganizationMatchesActiveOrganization(organizationId)) + { + return OrganizationDataReadAccessLevel.All; + } + + switch (GetCrossOrganizationReadAccess()) + { + case CrossOrganizationDataReadAccessLevel.Public: + return OrganizationDataReadAccessLevel.Public; + case CrossOrganizationDataReadAccessLevel.All: + return OrganizationDataReadAccessLevel.All; + default: + return OrganizationDataReadAccessLevel.None; + } + } + + public bool AllowReads(IEntity entity) + { + var result = false; + + if (IsGlobalAdmin()) + { + result = true; + } + else if (EntityEqualsActiveUser(entity)) + { + result = true; + } + else if (IsContextBound(entity)) + { + if (ActiveContextIsEntityContext(entity)) + { + result = true; + } + else if (GetCrossOrganizationReadAccess() >= CrossOrganizationDataReadAccessLevel.Public && EntityIsShared(entity)) + { + result = true; + } + } + + return result; + } + + public bool AllowCreate() + { + if (IsReadOnly()) + { + return false; + } + + if (MatchType()) + { + return IsGlobalAdmin(); + } + + //NOTE: Once we migrate more types, this will be extended + return true; + } + + public bool AllowCreate(IEntity entity) + { + return + AllowCreate() && + AllowModify(entity); //NOTE: Ensures backwards compatibility as long as some terms are yet to be fully migrated + } + + public bool AllowModify(IEntity entity) + { + var result = false; + + var ignoreReadOnlyRole = false; + + if (IsGlobalAdmin()) + { + result = true; + } + else if (EntityEqualsActiveUser(entity)) + { + ignoreReadOnlyRole = true; + result = true; + } + else if (IsContextBound(entity)) + { + if (ActiveContextIsEntityContext(entity)) + { + result = + IsLocalAdmin() || + AllowWritesToEntity(entity) || + HasAssignedWriteAccess(entity); + } + } + else + { + result = AllowWritesToEntity(entity); + } + + //If result is TRUE, this can be negated if read-only is not ignored AND user is marked as read-only + return result && (ignoreReadOnlyRole || IsReadOnly() == false); + } + + public bool AllowDelete(IEntity entity) + { + var result = false; + if (AllowModify(entity)) + { + switch (entity) + { + case ItSystem _: + result = + IsGlobalAdmin() || + (IsLocalAdmin() && ActiveContextIsEntityContext(entity)); + break; + default: + result = true; + break; + } + } + + return result; + } + + public bool AllowEntityVisibilityControl(IEntity entity) + { + return AllowModify(entity) && _activeUserContext.CanChangeVisibilityOf(entity); + } + + private bool AllowWritesToEntity(IEntity entity) + { + var result = false; + + if (HasModuleLevelWriteAccess(entity)) + { + result = true; + } + else if (IsUserEntity(entity) == false && HasOwnership(entity)) + { + result = true; + } + + return result; + } + + private bool HasModuleLevelWriteAccess(IEntity entity) + { + return _activeUserContext.HasModuleLevelAccessTo(entity); + } + + private static bool IsUserEntity(IEntity entity) + { + return entity is User; + } + + private static bool EntityIsShared(IEntity entity) + { + //Only return true if entity supports cross-organization sharing and access is marked as public + return (entity as IHasAccessModifier)?.AccessModifier == AccessModifier.Public; + } + + private bool IsUserInMunicipality() + { + return _activeUserContext.IsActiveInOrganizationOfType(OrganizationCategory.Municipality); + } + + private bool TargetOrganizationMatchesActiveOrganization(int targetOrganizationId) + { + return _activeUserContext.IsActiveInOrganization(targetOrganizationId); + } + + private bool HasAssignedWriteAccess(IEntity entity) + { + return _activeUserContext.HasAssignedWriteAccess(entity); + } + + private static bool IsContextBound(IEntity entity) + { + return entity is IContextAware || entity is IHasOrganization; + } + + private bool ActiveContextIsEntityContext(IEntity entity) + { + return _activeUserContext.IsActiveInSameOrganizationAs(entity); + } + + private bool HasOwnership(IEntity ownedEntity) + { + return _activeUserContext.HasOwnership(ownedEntity); + } + + private bool IsGlobalAdmin() + { + return _activeUserContext.HasRole(OrganizationRole.GlobalAdmin); + } + + private bool IsReadOnly() + { + return _activeUserContext.HasRole(OrganizationRole.ReadOnly); + } + + private bool IsLocalAdmin() + { + return _activeUserContext.HasRole(OrganizationRole.LocalAdmin); + } + + private bool EntityEqualsActiveUser(IEntity entity) + { + return IsUserEntity(entity) && entity.Id == _activeUserContext.UserId; + } + + private static bool MatchType() + { + return typeof(TLeft) == typeof(TRight); + } + } +} \ No newline at end of file diff --git a/Core.ApplicationServices/Authorization/OrganizationalUserContext.cs b/Core.ApplicationServices/Authorization/OrganizationalUserContext.cs new file mode 100644 index 0000000000..e786de0ee7 --- /dev/null +++ b/Core.ApplicationServices/Authorization/OrganizationalUserContext.cs @@ -0,0 +1,120 @@ +using System.Collections.Generic; +using Core.DomainModel; +using Core.DomainModel.ItContract; +using Core.DomainModel.ItProject; +using Core.DomainModel.ItSystem; +using Core.DomainModel.Organization; +using Core.DomainModel.Reports; + +namespace Core.ApplicationServices.Authorization +{ + /// + /// Determines the user in a specific organizational context + /// + public class OrganizationalUserContext : IOrganizationalUserContext + { + private readonly ISet _supportedFeatures; + private readonly ISet _roles; + private readonly User _user; + + public OrganizationalUserContext( + IEnumerable supportedFeatures, + IEnumerable roles, + User user, + int activeOrganizationId) + { + _user = user; + ActiveOrganizationId = activeOrganizationId; + _supportedFeatures = new HashSet(supportedFeatures); + _roles = new HashSet(roles); + } + + public int ActiveOrganizationId { get; } + + public int UserId => _user.Id; + + public bool IsActiveInOrganizationOfType(OrganizationCategory category) + { + return _user.DefaultOrganization?.Type?.Category == category; + } + + public bool HasRole(OrganizationRole role) + { + return _roles.Contains(role); + } + + public bool HasModuleLevelAccessTo(IEntity entity) + { + var featureToCheck = default(Feature?); + switch (entity) + { + case IContractModule _: + featureToCheck = Feature.CanModifyContracts; + break; + case IOrganizationModule _: + featureToCheck = Feature.CanModifyOrganizations; + break; + case IProjectModule _: + featureToCheck = Feature.CanModifyProjects; + break; + case ISystemModule _: + featureToCheck = Feature.CanModifySystems; + break; + case IReportModule _: + featureToCheck = Feature.CanModifyReports; + break; + case User _: + featureToCheck = Feature.CanModifyUsers; + break; + } + + return featureToCheck.HasValue && _supportedFeatures.Contains(featureToCheck.Value); + } + + public bool IsActiveInOrganization(int organizationId) + { + return ActiveOrganizationId == organizationId; + } + + public bool IsActiveInSameOrganizationAs(IEntity entity) + { + switch (entity) + { + case IContextAware contextAware: + return contextAware.IsInContext(ActiveOrganizationId); + case IHasOrganization hasOrg: + return IsActiveInOrganization(hasOrg.OrganizationId); + default: + return false; + } + } + + public bool HasAssignedWriteAccess(IEntity entity) + { + return entity.HasUserWriteAccess(_user); + } + + public bool HasOwnership(IEntity entity) + { + return entity.ObjectOwnerId == UserId; + } + + public bool CanChangeVisibilityOf(IEntity entity) + { + if (entity is IHasAccessModifier) + { + switch (entity) + { + case IContractModule _: + return _supportedFeatures.Contains(Feature.CanSetContractElementsAccessModifierToPublic); + case IOrganizationModule _: + return _supportedFeatures.Contains(Feature.CanSetOrganizationAccessModifierToPublic); + } + + return _supportedFeatures.Contains(Feature.CanSetAccessModifierToPublic); + } + + return false; + } + } +} \ No newline at end of file diff --git a/Core.ApplicationServices/Authorization/UnauthenticatedAuthorizationContext.cs b/Core.ApplicationServices/Authorization/UnauthenticatedAuthorizationContext.cs new file mode 100644 index 0000000000..df4f130ff3 --- /dev/null +++ b/Core.ApplicationServices/Authorization/UnauthenticatedAuthorizationContext.cs @@ -0,0 +1,48 @@ +using Core.DomainModel; +using Core.DomainServices.Authorization; + +namespace Core.ApplicationServices.Authorization +{ + public class UnauthenticatedAuthorizationContext : IAuthorizationContext + { + public CrossOrganizationDataReadAccessLevel GetCrossOrganizationReadAccess() + { + return CrossOrganizationDataReadAccessLevel.None; + } + + public OrganizationDataReadAccessLevel GetOrganizationReadAccessLevel(int organizationId) + { + return OrganizationDataReadAccessLevel.None; + } + + public bool AllowReads(IEntity entity) + { + return false; + } + + public bool AllowCreate() + { + return false; + } + + public bool AllowCreate(IEntity entity) + { + return false; + } + + public bool AllowModify(IEntity entity) + { + return false; + } + + public bool AllowDelete(IEntity entity) + { + return false; + } + + public bool AllowEntityVisibilityControl(IEntity entity) + { + return false; + } + } +} \ No newline at end of file diff --git a/Core.ApplicationServices/Authorization/UnauthenticatedUserContext.cs b/Core.ApplicationServices/Authorization/UnauthenticatedUserContext.cs new file mode 100644 index 0000000000..8c817d5122 --- /dev/null +++ b/Core.ApplicationServices/Authorization/UnauthenticatedUserContext.cs @@ -0,0 +1,54 @@ +using Core.DomainModel; +using Core.DomainModel.Organization; + +namespace Core.ApplicationServices.Authorization +{ + public class UnauthenticatedUserContext : IOrganizationalUserContext + + { + private const int INVALID_ID = -1; + + public int ActiveOrganizationId { get; } = INVALID_ID; + public int UserId { get; } = INVALID_ID; + + public bool IsActiveInOrganizationOfType(OrganizationCategory category) + { + return false; + } + + public bool HasRole(OrganizationRole role) + { + return false; + } + + public bool HasModuleLevelAccessTo(IEntity entity) + { + return false; + } + + public bool IsActiveInOrganization(int organizationId) + { + return false; + } + + public bool IsActiveInSameOrganizationAs(IEntity entity) + { + return false; + } + + public bool HasAssignedWriteAccess(IEntity entity) + { + return false; + } + + public bool HasOwnership(IEntity entity) + { + return false; + } + + public bool CanChangeVisibilityOf(IEntity entity) + { + return false; + } + } +} diff --git a/Core.ApplicationServices/Authorization/UserContextFactory.cs b/Core.ApplicationServices/Authorization/UserContextFactory.cs new file mode 100644 index 0000000000..4eec52ef2e --- /dev/null +++ b/Core.ApplicationServices/Authorization/UserContextFactory.cs @@ -0,0 +1,43 @@ +using System; +using System.Linq; +using Core.DomainServices; + +namespace Core.ApplicationServices.Authorization +{ + public class UserContextFactory : IUserContextFactory + { + private readonly IUserRepository _userRepository; + private readonly IFeatureChecker _featureChecker; + private readonly IOrganizationRoleService _roleService; + + public UserContextFactory( + IUserRepository userRepository, + IFeatureChecker featureChecker, + IOrganizationRoleService roleService) + { + _userRepository = userRepository; + _featureChecker = featureChecker; + _roleService = roleService; + } + + public IOrganizationalUserContext Create(int userId, int organizationId) + { + var user = _userRepository.GetByKey(userId); + if (user == null) + { + throw new InvalidOperationException($"Cannot create user context for invalid user ID:{userId}"); + } + + //Get roles for the organization + var organizationRoles = _roleService.GetRolesInOrganization(user, organizationId); + + var supportedFeatures = + Enum.GetValues(typeof(Feature)) + .Cast() + .Where(x => _featureChecker.CanExecute(user, x)) + .ToList(); + + return new OrganizationalUserContext(supportedFeatures, organizationRoles, user, organizationId); + } + } +} \ No newline at end of file diff --git a/Core.ApplicationServices/Core.ApplicationServices.csproj b/Core.ApplicationServices/Core.ApplicationServices.csproj index e162cfb92c..4be8902a38 100644 --- a/Core.ApplicationServices/Core.ApplicationServices.csproj +++ b/Core.ApplicationServices/Core.ApplicationServices.csproj @@ -34,51 +34,6 @@ prompt 4 - - true - bin\Debug %40 Test Mijlø\ - DEBUG;TRACE - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\Sandbox\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - true - bin\AppVeyor\ - DEBUG;TRACE - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\Test\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\Prod\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - ..\packages\Hangfire.Core.1.6.6\lib\net45\Hangfire.Core.dll @@ -126,12 +81,25 @@ + + + + + + + + + + + + + + - @@ -161,6 +129,10 @@ {ADCACC1D-F538-464C-9102-F4C1D6FA35D3} Core.DomainServices + + {0326cae6-87a1-4d66-84ae-eb8ce0340e9f} + Infrastructure.Services + diff --git a/Core.ApplicationServices/ExcelService.cs b/Core.ApplicationServices/ExcelService.cs index ea13fb4238..2673d009a2 100644 --- a/Core.ApplicationServices/ExcelService.cs +++ b/Core.ApplicationServices/ExcelService.cs @@ -8,6 +8,7 @@ using Core.DomainModel.ItContract; using Core.DomainModel.Organization; using Core.DomainServices; +using Infrastructure.Services.Cryptography; namespace Core.ApplicationServices { @@ -18,18 +19,21 @@ public class ExcelService : IExcelService private readonly IGenericRepository _itContractRepository; private readonly IGenericRepository _orgRightRepository; private readonly IExcelHandler _excelHandler; + private readonly ICryptoService _cryptoService; public ExcelService(IGenericRepository orgUnitRepository, IGenericRepository userRepository, IGenericRepository itContractRepository, IGenericRepository orgRightRepository, - IExcelHandler excelHandler) + IExcelHandler excelHandler, + ICryptoService cryptoService) { _orgUnitRepository = orgUnitRepository; _userRepository = userRepository; _itContractRepository = itContractRepository; _orgRightRepository = orgRightRepository; _excelHandler = excelHandler; + _cryptoService = cryptoService; } /// @@ -366,7 +370,7 @@ private IEnumerable ImportUsersTransaction(DataTable userTable LastChanged = DateTime.UtcNow, IsGlobalAdmin = false, Password = "mangler at blive indsat", - Salt = "mangler at blive indsat" + Salt = _cryptoService.Encrypt(Guid.NewGuid().ToString("N")) }; // if user dosnt exist create a new one. diff --git a/Core.ApplicationServices/FeatureChecker.cs b/Core.ApplicationServices/FeatureChecker.cs index 02d21cfe8f..7f906f4acc 100644 --- a/Core.ApplicationServices/FeatureChecker.cs +++ b/Core.ApplicationServices/FeatureChecker.cs @@ -1,7 +1,9 @@ using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using Core.DomainModel; using Core.DomainModel.Organization; +using Core.DomainServices; namespace Core.ApplicationServices { @@ -9,8 +11,6 @@ public enum Feature { MakeGlobalAdmin = 1, MakeLocalAdmin, - MakeReportAdmin, - MakeOrganization, CanSetAccessModifierToPublic, CanSetOrganizationTypeKommune, CanSetOrganizationTypeInteressefællesskab, @@ -28,51 +28,49 @@ public enum Feature public class FeatureChecker : IFeatureChecker { - private Dictionary> _features; + private readonly IOrganizationRoleService _roleService; + private static readonly IReadOnlyDictionary> Features; - public FeatureChecker() + static FeatureChecker() { - Init(); + Features = new ReadOnlyDictionary>(new Dictionary> + { + {Feature.MakeGlobalAdmin, new HashSet {OrganizationRole.GlobalAdmin}}, + {Feature.MakeLocalAdmin, new HashSet {OrganizationRole.GlobalAdmin, OrganizationRole.LocalAdmin}}, + {Feature.CanSetOrganizationTypeKommune, new HashSet {OrganizationRole.GlobalAdmin}}, + {Feature.CanSetOrganizationTypeInteressefællesskab, new HashSet {OrganizationRole.GlobalAdmin, OrganizationRole.LocalAdmin}}, + {Feature.CanSetOrganizationTypeVirksomhed, new HashSet {OrganizationRole.GlobalAdmin, OrganizationRole.LocalAdmin}}, + {Feature.CanSetOrganizationTypeAndenOffentligMyndighed, new HashSet {OrganizationRole.GlobalAdmin}}, + {Feature.CanSetAccessModifierToPublic, new HashSet {OrganizationRole.GlobalAdmin}}, + {Feature.CanSetOrganizationAccessModifierToPublic, new HashSet {OrganizationRole.GlobalAdmin, OrganizationRole.LocalAdmin}}, + {Feature.CanModifyUsers, new HashSet {OrganizationRole.GlobalAdmin, OrganizationRole.LocalAdmin, OrganizationRole.OrganizationModuleAdmin } }, + {Feature.CanModifyContracts, new HashSet {OrganizationRole.GlobalAdmin, OrganizationRole.LocalAdmin, OrganizationRole.ContractModuleAdmin } }, + {Feature.CanModifyOrganizations, new HashSet {OrganizationRole.GlobalAdmin, OrganizationRole.LocalAdmin, OrganizationRole.OrganizationModuleAdmin } }, + {Feature.CanModifyProjects, new HashSet {OrganizationRole.GlobalAdmin, OrganizationRole.LocalAdmin, OrganizationRole.ProjectModuleAdmin } }, + {Feature.CanModifySystems, new HashSet {OrganizationRole.GlobalAdmin, OrganizationRole.LocalAdmin, OrganizationRole.SystemModuleAdmin} }, + {Feature.CanModifyReports, new HashSet {OrganizationRole.GlobalAdmin, OrganizationRole.LocalAdmin, OrganizationRole.ReportModuleAdmin} }, + {Feature.CanSetContractElementsAccessModifierToPublic, new HashSet {OrganizationRole.GlobalAdmin, OrganizationRole.LocalAdmin, OrganizationRole.ContractModuleAdmin} } + }); } - public bool CanExecute(User user, Feature feature) + public FeatureChecker(IOrganizationRoleService roleService) { - var userRoles = CreateRoleList(user); - var featureRoles = _features[feature]; - return userRoles.Any(userRole => featureRoles.Contains(userRole)); + _roleService = roleService; } - private static IEnumerable CreateRoleList(User user) + public bool CanExecute(User user, Feature feature) { - var roles = user.OrganizationRights.Where(or => or.OrganizationId == user.DefaultOrganizationId).Select(x => x.Role).Distinct().ToList(); - if (user.IsGlobalAdmin) - roles.Add(OrganizationRole.GlobalAdmin); - - return roles; + var userRoles = CreateRoleList(user); + if (Features.TryGetValue(feature, out var featureRoles)) + { + return userRoles.Any(userRole => featureRoles.Contains(userRole)); + } + return false; } - private void Init() + private IEnumerable CreateRoleList(User user) { - _features = new Dictionary> - { - {Feature.MakeGlobalAdmin, new List {OrganizationRole.GlobalAdmin}}, - {Feature.MakeLocalAdmin, new List {OrganizationRole.GlobalAdmin, OrganizationRole.LocalAdmin}}, - {Feature.CanSetOrganizationTypeKommune, new List {OrganizationRole.GlobalAdmin}}, - {Feature.CanSetOrganizationTypeInteressefællesskab, new List {OrganizationRole.GlobalAdmin, OrganizationRole.LocalAdmin}}, - {Feature.CanSetOrganizationTypeVirksomhed, new List {OrganizationRole.GlobalAdmin, OrganizationRole.LocalAdmin}}, - {Feature.CanSetOrganizationTypeAndenOffentligMyndighed, new List {OrganizationRole.GlobalAdmin}}, - {Feature.CanSetAccessModifierToPublic, new List {OrganizationRole.GlobalAdmin}}, - {Feature.CanSetOrganizationAccessModifierToPublic, new List {OrganizationRole.GlobalAdmin, OrganizationRole.LocalAdmin}}, - {Feature.CanModifyUsers, new List {OrganizationRole.GlobalAdmin, OrganizationRole.LocalAdmin, OrganizationRole.OrganizationModuleAdmin } }, - {Feature.CanModifyContracts, new List {OrganizationRole.GlobalAdmin, OrganizationRole.LocalAdmin, OrganizationRole.ContractModuleAdmin } }, - {Feature.CanModifyOrganizations, new List {OrganizationRole.GlobalAdmin, OrganizationRole.LocalAdmin, OrganizationRole.OrganizationModuleAdmin } }, - {Feature.CanModifyProjects, new List {OrganizationRole.GlobalAdmin, OrganizationRole.LocalAdmin, OrganizationRole.ProjectModuleAdmin } }, - {Feature.CanModifySystems, new List {OrganizationRole.GlobalAdmin, OrganizationRole.LocalAdmin, OrganizationRole.SystemModuleAdmin} }, - {Feature.CanModifyReports, new List {OrganizationRole.GlobalAdmin, OrganizationRole.LocalAdmin, OrganizationRole.ReportModuleAdmin} }, - {Feature.CanSetContractElementsAccessModifierToPublic, new List {OrganizationRole.GlobalAdmin, OrganizationRole.LocalAdmin, OrganizationRole.ContractModuleAdmin} } - }; + return _roleService.GetRolesInOrganization(user, user.DefaultOrganizationId.GetValueOrDefault()); } - - } } \ No newline at end of file diff --git a/Core.ApplicationServices/IAdviceService.cs b/Core.ApplicationServices/IAdviceService.cs index e987381196..c0c4a45c84 100644 --- a/Core.ApplicationServices/IAdviceService.cs +++ b/Core.ApplicationServices/IAdviceService.cs @@ -1,12 +1,11 @@ -using Core.DomainModel; -using Core.DomainModel.Advice; +using Core.DomainModel.Advice; using System.Collections.Generic; namespace Core.ApplicationServices { public interface IAdviceService { - bool sendAdvice(int id); + bool SendAdvice(int id); IEnumerable GetAdvicesForOrg(int orgKey); } } diff --git a/Core.ApplicationServices/IAuthenticationService.cs b/Core.ApplicationServices/IAuthenticationService.cs index 60b1758982..a31a11f8ef 100644 --- a/Core.ApplicationServices/IAuthenticationService.cs +++ b/Core.ApplicationServices/IAuthenticationService.cs @@ -13,7 +13,6 @@ public interface IAuthenticationService /// Returns true if the user have write access to the given instance, else false. bool HasWriteAccess(int userId, IEntity entity); bool IsGlobalAdmin(int userId); - bool IsLocalAdmin(int userId, int organizationId); bool IsLocalAdmin(int userId); bool HasReadAccessOutsideContext(int userId); int GetCurrentOrganizationId(int userId); diff --git a/Core.ApplicationServices/ItContractService.cs b/Core.ApplicationServices/ItContractService.cs index 331a6fdeaf..cfc7a408a6 100644 --- a/Core.ApplicationServices/ItContractService.cs +++ b/Core.ApplicationServices/ItContractService.cs @@ -1,5 +1,4 @@ using Core.DomainModel.ItContract; -using Core.DomainModel.ItSystem; using Core.DomainServices; using System.Linq; diff --git a/Core.ApplicationServices/ItInterfaceService.cs b/Core.ApplicationServices/ItInterfaceService.cs index 9180e3ac1f..287c90612a 100644 --- a/Core.ApplicationServices/ItInterfaceService.cs +++ b/Core.ApplicationServices/ItInterfaceService.cs @@ -1,6 +1,4 @@ -// Udkommenteret ifm. OS2KITOS-663 - -using Core.DomainModel.ItSystem; +using Core.DomainModel.ItSystem; using Core.DomainServices; using System.Linq; @@ -9,17 +7,11 @@ namespace Core.ApplicationServices public class ItInterfaceService : IItInterfaceService { private readonly IGenericRepository _dataRowRepository; - private readonly IGenericRepository _exhibitRepository; private readonly IGenericRepository _repository; - //private readonly IGenericRepository _useRepository; - - //public ItInterfaceService(IGenericRepository repository, IGenericRepository dataRowRepository, IGenericRepository exhibitRepository, IGenericRepository useRepository) - public ItInterfaceService(IGenericRepository repository, IGenericRepository dataRowRepository, IGenericRepository exhibitRepository) + public ItInterfaceService(IGenericRepository repository, IGenericRepository dataRowRepository) { _repository = repository; _dataRowRepository = dataRowRepository; - _exhibitRepository = exhibitRepository; - //_useRepository = useRepository; } public void Delete(int id) { diff --git a/Core.ApplicationServices/ItSystemService.cs b/Core.ApplicationServices/ItSystemService.cs index 44ecd3b54f..f9a9af942b 100644 --- a/Core.ApplicationServices/ItSystemService.cs +++ b/Core.ApplicationServices/ItSystemService.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Core.DomainModel; using Core.DomainModel.ItSystem; using Core.DomainServices; diff --git a/Core.ApplicationServices/ItSystemUsageService.cs b/Core.ApplicationServices/ItSystemUsageService.cs index 2999058003..f960047a81 100644 --- a/Core.ApplicationServices/ItSystemUsageService.cs +++ b/Core.ApplicationServices/ItSystemUsageService.cs @@ -1,5 +1,4 @@ using Core.DomainModel; -using Core.DomainModel.ItSystem.DataTypes; using Core.DomainModel.ItSystemUsage; using Core.DomainServices; using System.Linq; @@ -9,17 +8,11 @@ namespace Core.ApplicationServices public class ItSystemUsageService : IItSystemUsageService { private readonly IGenericRepository _usageRepository; - private readonly IGenericRepository _interfaceUsageRepository; - private readonly IGenericRepository _dataRowUsageRepository; public ItSystemUsageService( - IGenericRepository usageRepository, - IGenericRepository interfaceUsageRepository, - IGenericRepository dataRowUsageRepository) + IGenericRepository usageRepository) { _usageRepository = usageRepository; - _interfaceUsageRepository = interfaceUsageRepository; - _dataRowUsageRepository = dataRowUsageRepository; } public ItSystemUsage Add(ItSystemUsage ItSystemUsage, User objectOwner) @@ -48,45 +41,5 @@ public void Delete(int id) _usageRepository.Delete(itSystemUsage); _usageRepository.Save(); } - - ///// - ///// Adds a new ItInterfaceUsage to an existing ItSystemUsage. - ///// - ///// The ItSystemUsage - ///// The new interface, which the ItInterfaceUsage should be generated from - //public void AddInterfaceUsage(ItSystemUsage usage, ItSystem theInterface) - //{ - // CreateAndInsertInterfaceUsage(theInterface.CanUseInterfaces, usage.ObjectOwner); - //} - - //private void CreateAndInsertInterfaceUsage(IEnumerable @interfaces, User objectOwner) - //{ - // foreach (var @interface in @interfaces) - // { - // var interfaceUsage = _interfaceUsageRepository.Create(); - // interfaceUsage.ItInterfaceExhibits = @interface; - // interfaceUsage.ObjectOwner = objectOwner; - // interfaceUsage.LastChangedByUser = objectOwner; - - // _interfaceUsageRepository.Insert(interfaceUsage); // saveChanges is called in callee - // // add data row usages - // CreateAndInsertDataRowUsage(@interface.DataRows, objectOwner); - // } - //} - - //private void CreateAndInsertDataRowUsage(IEnumerable dataRows, User objectOwner) - //{ - // foreach (var dataRow in dataRows) - // { - // var dataRowUsage = new DataRowUsage() - // { - // DataRowId = dataRow.Id, - // ObjectOwner = objectOwner, - // LastChangedByUser = objectOwner - // }; - - // _dataRowUsageRepository.Insert(dataRowUsage); // saveChanges is called in callee - // } - //} } } diff --git a/Core.ApplicationServices/MailClient.cs b/Core.ApplicationServices/MailClient.cs index d6880fe1e3..1ce7f847b1 100644 --- a/Core.ApplicationServices/MailClient.cs +++ b/Core.ApplicationServices/MailClient.cs @@ -1,11 +1,12 @@ -using Core.DomainServices; +using System; +using Core.DomainServices; using System.Net.Mail; namespace Core.ApplicationServices { - public class MailClient : IMailClient + public class MailClient : IMailClient, IDisposable { - public readonly SmtpClient _client; + private readonly SmtpClient _client; /// /// Construct a smtp client with configuration from web.config @@ -34,5 +35,10 @@ public void Send(MailMessage message) { _client.Send(message); } + + public void Dispose() + { + _client?.Dispose(); + } } } diff --git a/Core.ApplicationServices/OrganizationRoleService.cs b/Core.ApplicationServices/OrganizationRoleService.cs index 0947c984ae..dc99af93f5 100644 --- a/Core.ApplicationServices/OrganizationRoleService.cs +++ b/Core.ApplicationServices/OrganizationRoleService.cs @@ -1,4 +1,6 @@ -using Core.DomainModel; +using System.Collections.Generic; +using System.Linq; +using Core.DomainModel; using Core.DomainModel.Organization; using Core.DomainServices; @@ -28,12 +30,6 @@ private OrganizationRight AddOrganizationRoleToUser(User user, Organization orga return result; } - private void RemoveOrganizationRoleServiceFromUser(User user, Organization organization, OrganizationRole organizationRole) - { - _organizationRights.DeleteByKey(organization.Id, organizationRole, user.Id); - _organizationRights.Save(); - } - public OrganizationRight MakeUser(User user, Organization organization, User kitosUser) { return AddOrganizationRoleToUser(user, organization, kitosUser, OrganizationRole.User); @@ -44,59 +40,25 @@ public OrganizationRight MakeLocalAdmin(User user, Organization organization, Us return AddOrganizationRoleToUser(user, organization, kitosUser, OrganizationRole.LocalAdmin); } - public void RemoveLocalAdmin(User user, Organization organization) + public IEnumerable GetRolesInOrganization(User user, int organizationId) { - RemoveOrganizationRoleServiceFromUser(user, organization, OrganizationRole.LocalAdmin); - } - - public OrganizationRight MakeOrganizationModuleAdmin(User user, Organization organization, User kitosUser) - { - return AddOrganizationRoleToUser(user,organization,kitosUser, OrganizationRole.OrganizationModuleAdmin); - } - - public void RemoveOrganizationModuleAdmin(User user, Organization organization) - { - RemoveOrganizationRoleServiceFromUser(user,organization, OrganizationRole.OrganizationModuleAdmin); - } - - public OrganizationRight MakeProjectModuleAdmin(User user, Organization organization, User kitosUser) - { - return AddOrganizationRoleToUser(user, organization, kitosUser, OrganizationRole.ProjectModuleAdmin); - } - - public void RemoveProjectModuleAdmin(User user, Organization organization) - { - RemoveOrganizationRoleServiceFromUser(user, organization, OrganizationRole.ProjectModuleAdmin); - } + var roles = + user + .OrganizationRights + .Where(or => or.OrganizationId == organizationId) + .Select(x => x.Role) + .ToList(); - public OrganizationRight MakeSystemModuleAdmin(User user, Organization organization, User kitosUser) - { - return AddOrganizationRoleToUser(user, organization, kitosUser, OrganizationRole.SystemModuleAdmin); - } - - public void RemoveSystemModuleAdmin(User user, Organization organization) - { - RemoveOrganizationRoleServiceFromUser(user, organization, OrganizationRole.SystemModuleAdmin); - } - - public OrganizationRight MakeContractModuleAdmin(User user, Organization organization, User kitosUser) - { - return AddOrganizationRoleToUser(user, organization, kitosUser, OrganizationRole.ContractModuleAdmin); - } - - public void RemoveContractModuleAdmin(User user, Organization organization) - { - RemoveOrganizationRoleServiceFromUser(user, organization, OrganizationRole.ContractModuleAdmin); - } - - public OrganizationRight MakeReportModuleAdmin(User user, Organization organization, User kitosUser) - { - return AddOrganizationRoleToUser(user, organization, kitosUser, OrganizationRole.ReportModuleAdmin); - } + //NOTE: Use of this property is somewhat messy. In some cases it applies the IsGlobalAdmin boolean (the right way) and in other cases it uses the "right" with the role "Global admin" which is the wrong way + if (user.IsGlobalAdmin) + { + roles.Add(OrganizationRole.GlobalAdmin); + } - public void RemoveReportModuleAdmin(User user, Organization organization) - { - RemoveOrganizationRoleServiceFromUser(user, organization, OrganizationRole.ReportModuleAdmin); + return roles + .Distinct() + .ToList() + .AsReadOnly(); } } } diff --git a/Core.ApplicationServices/OrganizationService.cs b/Core.ApplicationServices/OrganizationService.cs index 21bf7bae37..51d3be3fdf 100644 --- a/Core.ApplicationServices/OrganizationService.cs +++ b/Core.ApplicationServices/OrganizationService.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Core.DomainModel; using Core.DomainModel.Organization; diff --git a/Core.ApplicationServices/ReferenceService.cs b/Core.ApplicationServices/ReferenceService.cs index b82ccc931c..81b275fa41 100644 --- a/Core.ApplicationServices/ReferenceService.cs +++ b/Core.ApplicationServices/ReferenceService.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Core.DomainModel; using Core.DomainServices; using Ninject; @@ -13,6 +12,7 @@ public class ReferenceService [Inject] public IGenericRepository ReferenceRepository { get; set; } + [Inject] public ILogger Logger { get; set; } diff --git a/Core.ApplicationServices/UserService.cs b/Core.ApplicationServices/UserService.cs index b892d75cbc..83a2535d9b 100644 --- a/Core.ApplicationServices/UserService.cs +++ b/Core.ApplicationServices/UserService.cs @@ -7,6 +7,7 @@ using Core.DomainModel.Organization; using Core.DomainServices; using System.Security.Cryptography; +using Infrastructure.Services.Cryptography; namespace Core.ApplicationServices { @@ -15,6 +16,8 @@ public class UserService : IUserService private readonly TimeSpan _ttl; private readonly string _baseUrl; private readonly string _mailSuffix; + private readonly string _defaultUserPassword; + private readonly bool _useDefaultUserPassword; private readonly IGenericRepository _userRepository; private readonly IGenericRepository _orgRepository; private readonly IGenericRepository _passwordResetRequestRepository; @@ -26,6 +29,8 @@ public class UserService : IUserService public UserService(TimeSpan ttl, string baseUrl, string mailSuffix, + string defaultUserPassword, + bool useDefaultUserPassword, IGenericRepository userRepository, IGenericRepository orgRepository, IGenericRepository passwordResetRequestRepository, @@ -35,25 +40,31 @@ public UserService(TimeSpan ttl, _ttl = ttl; _baseUrl = baseUrl; _mailSuffix = mailSuffix; + _defaultUserPassword = defaultUserPassword; + _useDefaultUserPassword = useDefaultUserPassword; _userRepository = userRepository; _orgRepository = orgRepository; _passwordResetRequestRepository = passwordResetRequestRepository; _mailClient = mailClient; _cryptoService = cryptoService; _crypt = new SHA256Managed(); + if (useDefaultUserPassword && string.IsNullOrWhiteSpace(defaultUserPassword)) + { + throw new ArgumentException(nameof(defaultUserPassword) + " must be defined, when it must be used."); + } } public User AddUser(User user, bool sendMailOnCreation, int orgId) { // hash his salt and default password - user.Salt = _cryptoService.Encrypt(DateTime.UtcNow + " spices"); -#if DEBUG - user.Password = _cryptoService.Encrypt("arne123" + user.Salt); //TODO: Don't use default password -#else - user.Password = _cryptoService.Encrypt(DateTime.UtcNow + user.Salt); -#endif - - user.LastChanged = DateTime.UtcNow; + var utcNow = DateTime.UtcNow; + user.Salt = _cryptoService.Encrypt(utcNow + " spices"); + + user.Password = _useDefaultUserPassword + ? _cryptoService.Encrypt(_defaultUserPassword + user.Salt) + : _cryptoService.Encrypt(utcNow + user.Salt); + + user.LastChanged = utcNow; user.DefaultOrganizationId = orgId; _userRepository.Insert(user); @@ -184,9 +195,19 @@ public void ResetPassword(PasswordResetRequest passwordResetRequest, string newP } - private bool IsValidPassword(string password) + private static bool IsValidPassword(string password) { return password.Length >= 6; } + + public User GetUserById(int id) + { + return _userRepository.GetByKey(id); + } + + public void Dispose() + { + _crypt?.Dispose(); + } } } diff --git a/Core.ApplicationServices/app.config b/Core.ApplicationServices/app.config index 9e66ad6484..200d7286f5 100644 --- a/Core.ApplicationServices/app.config +++ b/Core.ApplicationServices/app.config @@ -18,6 +18,18 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/Core.DomainModel/Advice/AdviceSent.cs b/Core.DomainModel/Advice/AdviceSent.cs index 1d7b4a05f2..ca0b7c0a92 100644 --- a/Core.DomainModel/Advice/AdviceSent.cs +++ b/Core.DomainModel/Advice/AdviceSent.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Core.DomainModel.AdviceSent { diff --git a/Core.DomainModel/App.config b/Core.DomainModel/App.config index 74f6c46866..109ee1b9a4 100644 --- a/Core.DomainModel/App.config +++ b/Core.DomainModel/App.config @@ -14,6 +14,18 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/Core.DomainModel/AttachedOption.cs b/Core.DomainModel/AttachedOption.cs index 52c4f94425..a19acecbda 100644 --- a/Core.DomainModel/AttachedOption.cs +++ b/Core.DomainModel/AttachedOption.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Core.DomainModel +namespace Core.DomainModel { public class AttachedOption : Entity { diff --git a/Core.DomainModel/ContactPerson.cs b/Core.DomainModel/ContactPerson.cs index cba4cfedb1..7b24a26dcb 100644 --- a/Core.DomainModel/ContactPerson.cs +++ b/Core.DomainModel/ContactPerson.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Core.DomainModel +namespace Core.DomainModel { public class ContactPerson : Entity { diff --git a/Core.DomainModel/Core.DomainModel.csproj b/Core.DomainModel/Core.DomainModel.csproj index bf0516d5fc..d736ee8e32 100644 --- a/Core.DomainModel/Core.DomainModel.csproj +++ b/Core.DomainModel/Core.DomainModel.csproj @@ -34,87 +34,14 @@ prompt 4 - - true - bin\Debug %40 Test Mijlø\ - DEBUG;TRACE - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\Sandbox\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - true - bin\AppVeyor\ - DEBUG;TRACE - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\Test\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\Prod\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - ..\packages\Microsoft.OData.Core.6.15.0\lib\portable-net45+win+wpa81\Microsoft.OData.Core.dll - True - - - ..\packages\Microsoft.OData.Edm.6.15.0\lib\portable-net45+win+wpa81\Microsoft.OData.Edm.dll - True - - - ..\packages\Microsoft.Spatial.6.15.0\lib\portable-net45+win+wpa81\Microsoft.Spatial.dll - True - - - ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll - True - - - ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - True - - - ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - True - - - ..\packages\Microsoft.AspNet.OData.5.9.1\lib\net45\System.Web.OData.dll - True - @@ -221,7 +148,6 @@ - @@ -280,8 +206,9 @@ - - + + Designer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/Infrastructure.DataAccess/Migrations/201908121031346_AddedHasApiAccessBooleanToUser.Designer.cs b/Infrastructure.DataAccess/Migrations/201908121031346_AddedHasApiAccessBooleanToUser.Designer.cs new file mode 100644 index 0000000000..0ac1dc9bde --- /dev/null +++ b/Infrastructure.DataAccess/Migrations/201908121031346_AddedHasApiAccessBooleanToUser.Designer.cs @@ -0,0 +1,29 @@ +// +namespace Infrastructure.DataAccess.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.1.3-40302")] + public sealed partial class AddedHasApiAccessBooleanToUser : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(AddedHasApiAccessBooleanToUser)); + + string IMigrationMetadata.Id + { + get { return "201908121031346_AddedHasApiAccessBooleanToUser"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/Infrastructure.DataAccess/Migrations/201908121031346_AddedHasApiAccessBooleanToUser.cs b/Infrastructure.DataAccess/Migrations/201908121031346_AddedHasApiAccessBooleanToUser.cs new file mode 100644 index 0000000000..0d2fdeb1be --- /dev/null +++ b/Infrastructure.DataAccess/Migrations/201908121031346_AddedHasApiAccessBooleanToUser.cs @@ -0,0 +1,18 @@ +namespace Infrastructure.DataAccess.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class AddedHasApiAccessBooleanToUser : DbMigration + { + public override void Up() + { + AddColumn("dbo.User", "HasApiAccess", c => c.Boolean()); + } + + public override void Down() + { + DropColumn("dbo.User", "HasApiAccess"); + } + } +} diff --git a/Infrastructure.DataAccess/Migrations/201908121031346_AddedHasApiAccessBooleanToUser.resx b/Infrastructure.DataAccess/Migrations/201908121031346_AddedHasApiAccessBooleanToUser.resx new file mode 100644 index 0000000000..0066a81ce3 --- /dev/null +++ b/Infrastructure.DataAccess/Migrations/201908121031346_AddedHasApiAccessBooleanToUser.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/Infrastructure.DataAccess/Migrations/201909021103202_Add-Access-Modifier-Indexes.Designer.cs b/Infrastructure.DataAccess/Migrations/201909021103202_Add-Access-Modifier-Indexes.Designer.cs new file mode 100644 index 0000000000..cd4e12569d --- /dev/null +++ b/Infrastructure.DataAccess/Migrations/201909021103202_Add-Access-Modifier-Indexes.Designer.cs @@ -0,0 +1,29 @@ +// +namespace Infrastructure.DataAccess.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.1.3-40302")] + public sealed partial class AddAccessModifierIndexes : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(AddAccessModifierIndexes)); + + string IMigrationMetadata.Id + { + get { return "201909021103202_Add-Access-Modifier-Indexes"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/Infrastructure.DataAccess/Migrations/201909021103202_Add-Access-Modifier-Indexes.cs b/Infrastructure.DataAccess/Migrations/201909021103202_Add-Access-Modifier-Indexes.cs new file mode 100644 index 0000000000..ee27daf64d --- /dev/null +++ b/Infrastructure.DataAccess/Migrations/201909021103202_Add-Access-Modifier-Indexes.cs @@ -0,0 +1,30 @@ +namespace Infrastructure.DataAccess.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class AddAccessModifierIndexes : DbMigration + { + public override void Up() + { + CreateIndex("dbo.ItSystem", "AccessModifier", name: "UX_AccessModifier"); + CreateIndex("dbo.Organization", "AccessModifier", name: "UX_AccessModifier"); + CreateIndex("dbo.ItInterface", "AccessModifier", name: "UX_AccessModifier"); + CreateIndex("dbo.ItProject", "AccessModifier", name: "UX_AccessModifier"); + CreateIndex("dbo.EconomyStream", "AccessModifier", name: "UX_AccessModifier"); + CreateIndex("dbo.TaskRef", "AccessModifier", name: "UX_AccessModifier"); + CreateIndex("dbo.Reports", "AccessModifier", name: "UX_AccessModifier"); + } + + public override void Down() + { + DropIndex("dbo.Reports", "UX_AccessModifier"); + DropIndex("dbo.TaskRef", "UX_AccessModifier"); + DropIndex("dbo.EconomyStream", "UX_AccessModifier"); + DropIndex("dbo.ItProject", "UX_AccessModifier"); + DropIndex("dbo.ItInterface", "UX_AccessModifier"); + DropIndex("dbo.Organization", "UX_AccessModifier"); + DropIndex("dbo.ItSystem", "UX_AccessModifier"); + } + } +} diff --git a/Infrastructure.DataAccess/Migrations/201909021103202_Add-Access-Modifier-Indexes.resx b/Infrastructure.DataAccess/Migrations/201909021103202_Add-Access-Modifier-Indexes.resx new file mode 100644 index 0000000000..6cb73c7d8c --- /dev/null +++ b/Infrastructure.DataAccess/Migrations/201909021103202_Add-Access-Modifier-Indexes.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/Infrastructure.DataAccess/Migrations/Configuration.cs b/Infrastructure.DataAccess/Migrations/Configuration.cs index de19f4f9c8..12948cbae9 100644 --- a/Infrastructure.DataAccess/Migrations/Configuration.cs +++ b/Infrastructure.DataAccess/Migrations/Configuration.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Data.Entity; using System.Diagnostics; -using Core.ApplicationServices; using Core.DomainModel; using Core.DomainModel.ItContract; using Core.DomainModel.ItProject; @@ -10,6 +9,7 @@ using Core.DomainModel.ItSystemUsage; using Core.DomainModel.Organization; using Core.DomainModel.Reports; +using Infrastructure.Services.Cryptography; namespace Infrastructure.DataAccess.Migrations { @@ -32,49 +32,38 @@ public Configuration() /// The context. protected override void Seed(KitosContext context) { - var newBuild = GetEnvironmentVariable("SeedNewDb") == "yes"; - if (newBuild) - { - Console.Out.WriteLine("Seeding initial data into kitos database"); - } + var cleanDatabase = GetEnvironmentVariable("SeedNewDb") == "yes"; - #region USERS - - // don't overwrite global admin if it already exists - // cause it'll overwrite UUID - var salt = $"{Guid.NewGuid()}{Guid.NewGuid()}{Guid.NewGuid()}"; - string password; - using (var cryptoService = new CryptoService()) + if (cleanDatabase) { - password = cryptoService.Encrypt($"{Guid.NewGuid()}{Guid.NewGuid()}{Guid.NewGuid()}" + salt); - } + Console.Out.WriteLine("Seeding initial data into kitos database"); + #region USERS - const string rootUserEmail = "support@kitos.dk"; - var globalAdmin = context.Users.FirstOrDefault(x => x.Email == rootUserEmail) ?? context.Users.Add( - new User + // don't overwrite global admin if it already exists + // cause it'll overwrite UUID + var salt = $"{Guid.NewGuid()}{Guid.NewGuid()}{Guid.NewGuid()}"; + string password; + using (var cryptoService = new CryptoService()) { - Name = "Global", - LastName = "admin", - Email = rootUserEmail, - Salt = salt, - Password = password, - IsGlobalAdmin = true - }); - + password = cryptoService.Encrypt($"{Guid.NewGuid()}{Guid.NewGuid()}{Guid.NewGuid()}" + salt); + } - //var user1 = CreateUser("Test bruger1", "1@test", "test", cryptoService); - //var user2 = CreateUser("Test bruger2", "2@test", "test", cryptoService); - //var user3 = CreateUser("Test bruger3", "3@test", "test", cryptoService); - //var user4 = CreateUser("Test bruger4", "4@test", "test", cryptoService); - //var user5 = CreateUser("Test bruger5", "5@test", "test", cryptoService); - //context.Users.AddOrUpdate(x => x.Email, , user1, user2, user3, user4, user5); + const string rootUserEmail = "support@kitos.dk"; + var globalAdmin = context.Users.FirstOrDefault(x => x.Email == rootUserEmail) ?? context.Users.Add( + new User + { + Name = "Global", + LastName = "admin", + Email = rootUserEmail, + Salt = salt, + Password = password, + IsGlobalAdmin = true + }); - context.SaveChanges(); + context.SaveChanges(); - #endregion + #endregion - if (newBuild == true) - { #region OPTIONS Console.Out.WriteLine("Initializing options"); diff --git a/Infrastructure.DataAccess/TypeConfigurationExtensions.cs b/Infrastructure.DataAccess/TypeConfigurationExtensions.cs index 89dab53920..1d10571594 100644 --- a/Infrastructure.DataAccess/TypeConfigurationExtensions.cs +++ b/Infrastructure.DataAccess/TypeConfigurationExtensions.cs @@ -1,6 +1,8 @@ using System.ComponentModel.DataAnnotations.Schema; using System.Data.Entity.Infrastructure.Annotations; using System.Data.Entity.ModelConfiguration.Configuration; +using Core.DomainModel; +using Infrastructure.DataAccess.Mapping; namespace Infrastructure.DataAccess { @@ -21,7 +23,24 @@ public static PrimitivePropertyConfiguration HasUniqueIndexAnnotation( string indexName, int columnOrder) { - var indexAttribute = new IndexAttribute(indexName, columnOrder) { IsUnique = true }; + return property.HasIndexAnnotation(indexName, columnOrder, true); + } + + /// + /// Creates a non-clustered + /// + /// + /// The index name. + /// A zero-based number which will be used to determine column ordering for multi-column indexes. + /// Determines if unique constraint should be applied + /// + public static PrimitivePropertyConfiguration HasIndexAnnotation( + this PrimitivePropertyConfiguration property, + string indexName, + int columnOrder, + bool unique = false) + { + var indexAttribute = new IndexAttribute(indexName, columnOrder) { IsUnique = unique }; var indexAnnotation = new IndexAnnotation(indexAttribute); return property.HasColumnAnnotation(IndexAnnotation.AnnotationName, indexAnnotation); diff --git a/Infrastructure.DataAccess/TypeMapping.cs b/Infrastructure.DataAccess/TypeMapping.cs new file mode 100644 index 0000000000..dc90dd09fd --- /dev/null +++ b/Infrastructure.DataAccess/TypeMapping.cs @@ -0,0 +1,23 @@ +using System.Data.Entity.ModelConfiguration.Configuration; +using Core.DomainModel; +using Infrastructure.DataAccess.Mapping; + +namespace Infrastructure.DataAccess +{ + public static class TypeMapping + { + /// + /// Creates a non-clustered index for access modifier + /// + /// Source entity mapping + /// + public static PrimitivePropertyConfiguration AddIndexOnAccessModifier(TMap map) + where TType : Entity, IHasAccessModifier + where TMap : EntityMap + { + return map + .Property(x => x.AccessModifier) + .HasIndexAnnotation("UX_AccessModifier", 0); + } + } +} diff --git a/Infrastructure.DataAccess/UserRepository.cs b/Infrastructure.DataAccess/UserRepository.cs index d39aa0cfca..644559ccd8 100644 --- a/Infrastructure.DataAccess/UserRepository.cs +++ b/Infrastructure.DataAccess/UserRepository.cs @@ -1,5 +1,4 @@ -using System; -using System.Linq; +using System.Linq; using Core.DomainServices; using Core.DomainModel; @@ -24,5 +23,10 @@ public User GetByUuid(string uniqueId) { return _context.Users.SingleOrDefault(u => u.UniqueId == uniqueId); } + + public User GetById(int id) + { + return _context.Users.SingleOrDefault(u => u.Id == id); + } } } \ No newline at end of file diff --git a/Infrastructure.OpenXML/Infrastructure.OpenXML.csproj b/Infrastructure.OpenXML/Infrastructure.OpenXML.csproj index e21fecf29b..1eff99e7d3 100644 --- a/Infrastructure.OpenXML/Infrastructure.OpenXML.csproj +++ b/Infrastructure.OpenXML/Infrastructure.OpenXML.csproj @@ -33,42 +33,6 @@ prompt 4 - - bin\Sandbox\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - true - bin\AppVeyor\ - DEBUG;TRACE - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\Test\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\Prod\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - True diff --git a/Infrastructure.OpenXML/app.config b/Infrastructure.OpenXML/app.config index 9e66ad6484..200d7286f5 100644 --- a/Infrastructure.OpenXML/app.config +++ b/Infrastructure.OpenXML/app.config @@ -18,6 +18,18 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/Core.ApplicationServices/CryptoService.cs b/Infrastructure.Services/Cryptography/CryptoService.cs similarity index 90% rename from Core.ApplicationServices/CryptoService.cs rename to Infrastructure.Services/Cryptography/CryptoService.cs index 0c3d61b458..e4f3218623 100644 --- a/Core.ApplicationServices/CryptoService.cs +++ b/Infrastructure.Services/Cryptography/CryptoService.cs @@ -1,9 +1,8 @@ using System.Security.Cryptography; using System.Text; using System.Web; -using Core.DomainServices; -namespace Core.ApplicationServices +namespace Infrastructure.Services.Cryptography { public class CryptoService : ICryptoService { diff --git a/Core.DomainServices/ICryptoService.cs b/Infrastructure.Services/Cryptography/ICryptoService.cs similarity index 71% rename from Core.DomainServices/ICryptoService.cs rename to Infrastructure.Services/Cryptography/ICryptoService.cs index 128d455893..3e630f791d 100644 --- a/Core.DomainServices/ICryptoService.cs +++ b/Infrastructure.Services/Cryptography/ICryptoService.cs @@ -1,6 +1,6 @@ using System; -namespace Core.DomainServices +namespace Infrastructure.Services.Cryptography { public interface ICryptoService : IDisposable { diff --git a/Infrastructure.Services/Infrastructure.Services.csproj b/Infrastructure.Services/Infrastructure.Services.csproj new file mode 100644 index 0000000000..4e111ecff5 --- /dev/null +++ b/Infrastructure.Services/Infrastructure.Services.csproj @@ -0,0 +1,51 @@ + + + + + Debug + AnyCPU + {0326CAE6-87A1-4D66-84AE-EB8CE0340E9F} + Library + Properties + Infrastructure.Services + Infrastructure.Services + v4.6.1 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Infrastructure.Services/Properties/AssemblyInfo.cs b/Infrastructure.Services/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..471aaa6f64 --- /dev/null +++ b/Infrastructure.Services/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Infrastructure.Services")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Infrastructure.Services")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("0326cae6-87a1-4d66-84ae-eb8ce0340e9f")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/KITOS.sln b/KITOS.sln index 1aefff3f42..f3338115b0 100644 --- a/KITOS.sln +++ b/KITOS.sln @@ -35,7 +35,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject .eslintrc = .eslintrc .gitignore = .gitignore - appveyor.yml = appveyor.yml bower.json = bower.json bundle.config.js = bundle.config.js DOCUMENTATION.md = DOCUMENTATION.md @@ -73,140 +72,86 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DeploymentScripts", "Deploy ProjectSection(SolutionItems) = preProject DeploymentScripts\AwsApi.ps1 = DeploymentScripts\AwsApi.ps1 DeploymentScripts\CheckWebsiteVersion.ps1 = DeploymentScripts\CheckWebsiteVersion.ps1 + DeploymentScripts\DbMigrations.ps1 = DeploymentScripts\DbMigrations.ps1 DeploymentScripts\DeployIntegration.ps1 = DeploymentScripts\DeployIntegration.ps1 DeploymentScripts\DeploymentSetup.ps1 = DeploymentScripts\DeploymentSetup.ps1 DeploymentScripts\DeployWebsite.ps1 = DeploymentScripts\DeployWebsite.ps1 + DeploymentScripts\DeployWebsiteToEnvironment.ps1 = DeploymentScripts\DeployWebsiteToEnvironment.ps1 + DeploymentScripts\PrepareCleanDeveloperDatabase.ps1 = DeploymentScripts\PrepareCleanDeveloperDatabase.ps1 DeploymentScripts\PrepareIntegrationDatabase.ps1 = DeploymentScripts\PrepareIntegrationDatabase.ps1 + DeploymentScripts\PrepareLocalDatabase.ps1 = DeploymentScripts\PrepareLocalDatabase.ps1 + DeploymentScripts\PrepareProductionDatabase.ps1 = DeploymentScripts\PrepareProductionDatabase.ps1 + DeploymentScripts\PrepareTestDatabase.ps1 = DeploymentScripts\PrepareTestDatabase.ps1 + DeploymentScripts\RunE2ETests.ps1 = DeploymentScripts\RunE2ETests.ps1 + DeploymentScripts\RunE2ETestsLocally.ps1 = DeploymentScripts\RunE2ETestsLocally.ps1 + DeploymentScripts\RunE2ETestsOnIntegration.ps1 = DeploymentScripts\RunE2ETestsOnIntegration.ps1 + DeploymentScripts\VpnCli.ps1 = DeploymentScripts\VpnCli.ps1 EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.Integration.Presentation.Web", "Tests.Integration.Presentation.Web\Tests.Integration.Presentation.Web.csproj", "{46D4551A-7301-456B-A9BA-1E0621C09112}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Unit", "Unit", "{8FD29D91-1CAC-4A14-B085-7631FA4E81DB}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Integration", "Integration", "{28A9664B-E3E1-4362-8E2D-6C5A0DED0517}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CiscoAnyConnectTool", "DeploymentTools\CiscoAnyConnectTool\CiscoAnyConnectTool.csproj", "{FE733415-7189-4EB7-A694-8D3B2BE27549}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Integration", "Integration", "{1A926F74-9D45-4E47-AD27-967027E1C64A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.Integration.Presentation.Web", "Tests.Integration.Presentation.Web\Tests.Integration.Presentation.Web.csproj", "{46D4551A-7301-456B-A9BA-1E0621C09112}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure.Services", "Infrastructure.Services\Infrastructure.Services.csproj", "{0326CAE6-87A1-4D66-84AE-EB8CE0340E9F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - AppVeyor|Any CPU = AppVeyor|Any CPU Debug|Any CPU = Debug|Any CPU - Prod|Any CPU = Prod|Any CPU Release|Any CPU = Release|Any CPU - Test|Any CPU = Test|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {A76A8E41-74F7-4443-A5F3-059B5414D83B}.AppVeyor|Any CPU.ActiveCfg = AppVeyor|Any CPU - {A76A8E41-74F7-4443-A5F3-059B5414D83B}.AppVeyor|Any CPU.Build.0 = AppVeyor|Any CPU {A76A8E41-74F7-4443-A5F3-059B5414D83B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A76A8E41-74F7-4443-A5F3-059B5414D83B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A76A8E41-74F7-4443-A5F3-059B5414D83B}.Prod|Any CPU.ActiveCfg = Prod|Any CPU - {A76A8E41-74F7-4443-A5F3-059B5414D83B}.Prod|Any CPU.Build.0 = Prod|Any CPU {A76A8E41-74F7-4443-A5F3-059B5414D83B}.Release|Any CPU.ActiveCfg = Release|Any CPU {A76A8E41-74F7-4443-A5F3-059B5414D83B}.Release|Any CPU.Build.0 = Release|Any CPU - {A76A8E41-74F7-4443-A5F3-059B5414D83B}.Test|Any CPU.ActiveCfg = Test|Any CPU - {A76A8E41-74F7-4443-A5F3-059B5414D83B}.Test|Any CPU.Build.0 = Test|Any CPU - {ADCACC1D-F538-464C-9102-F4C1D6FA35D3}.AppVeyor|Any CPU.ActiveCfg = AppVeyor|Any CPU - {ADCACC1D-F538-464C-9102-F4C1D6FA35D3}.AppVeyor|Any CPU.Build.0 = AppVeyor|Any CPU {ADCACC1D-F538-464C-9102-F4C1D6FA35D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {ADCACC1D-F538-464C-9102-F4C1D6FA35D3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ADCACC1D-F538-464C-9102-F4C1D6FA35D3}.Prod|Any CPU.ActiveCfg = Prod|Any CPU - {ADCACC1D-F538-464C-9102-F4C1D6FA35D3}.Prod|Any CPU.Build.0 = Prod|Any CPU {ADCACC1D-F538-464C-9102-F4C1D6FA35D3}.Release|Any CPU.ActiveCfg = Release|Any CPU {ADCACC1D-F538-464C-9102-F4C1D6FA35D3}.Release|Any CPU.Build.0 = Release|Any CPU - {ADCACC1D-F538-464C-9102-F4C1D6FA35D3}.Test|Any CPU.ActiveCfg = Test|Any CPU - {ADCACC1D-F538-464C-9102-F4C1D6FA35D3}.Test|Any CPU.Build.0 = Test|Any CPU - {F0986888-8F35-4559-8FBA-428BC9EC9ADD}.AppVeyor|Any CPU.ActiveCfg = AppVeyor|Any CPU - {F0986888-8F35-4559-8FBA-428BC9EC9ADD}.AppVeyor|Any CPU.Build.0 = AppVeyor|Any CPU {F0986888-8F35-4559-8FBA-428BC9EC9ADD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F0986888-8F35-4559-8FBA-428BC9EC9ADD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F0986888-8F35-4559-8FBA-428BC9EC9ADD}.Prod|Any CPU.ActiveCfg = Prod|Any CPU - {F0986888-8F35-4559-8FBA-428BC9EC9ADD}.Prod|Any CPU.Build.0 = Prod|Any CPU {F0986888-8F35-4559-8FBA-428BC9EC9ADD}.Release|Any CPU.ActiveCfg = Release|Any CPU {F0986888-8F35-4559-8FBA-428BC9EC9ADD}.Release|Any CPU.Build.0 = Release|Any CPU - {F0986888-8F35-4559-8FBA-428BC9EC9ADD}.Test|Any CPU.ActiveCfg = Test|Any CPU - {F0986888-8F35-4559-8FBA-428BC9EC9ADD}.Test|Any CPU.Build.0 = Test|Any CPU - {6CD15363-5401-43C5-9479-02FDDFA881DC}.AppVeyor|Any CPU.ActiveCfg = AppVeyor|Any CPU - {6CD15363-5401-43C5-9479-02FDDFA881DC}.AppVeyor|Any CPU.Build.0 = AppVeyor|Any CPU {6CD15363-5401-43C5-9479-02FDDFA881DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6CD15363-5401-43C5-9479-02FDDFA881DC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6CD15363-5401-43C5-9479-02FDDFA881DC}.Prod|Any CPU.ActiveCfg = Prod|Any CPU - {6CD15363-5401-43C5-9479-02FDDFA881DC}.Prod|Any CPU.Build.0 = Prod|Any CPU {6CD15363-5401-43C5-9479-02FDDFA881DC}.Release|Any CPU.ActiveCfg = Release|Any CPU {6CD15363-5401-43C5-9479-02FDDFA881DC}.Release|Any CPU.Build.0 = Release|Any CPU - {6CD15363-5401-43C5-9479-02FDDFA881DC}.Test|Any CPU.ActiveCfg = Test|Any CPU - {6CD15363-5401-43C5-9479-02FDDFA881DC}.Test|Any CPU.Build.0 = Test|Any CPU - {E75385A3-EA7C-4DFF-B989-BEE64BC506ED}.AppVeyor|Any CPU.ActiveCfg = Test|Any CPU - {E75385A3-EA7C-4DFF-B989-BEE64BC506ED}.AppVeyor|Any CPU.Build.0 = Test|Any CPU {E75385A3-EA7C-4DFF-B989-BEE64BC506ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E75385A3-EA7C-4DFF-B989-BEE64BC506ED}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E75385A3-EA7C-4DFF-B989-BEE64BC506ED}.Prod|Any CPU.ActiveCfg = Prod|Any CPU - {E75385A3-EA7C-4DFF-B989-BEE64BC506ED}.Prod|Any CPU.Build.0 = Prod|Any CPU {E75385A3-EA7C-4DFF-B989-BEE64BC506ED}.Release|Any CPU.ActiveCfg = Release|Any CPU {E75385A3-EA7C-4DFF-B989-BEE64BC506ED}.Release|Any CPU.Build.0 = Release|Any CPU - {E75385A3-EA7C-4DFF-B989-BEE64BC506ED}.Release|Any CPU.Deploy.0 = Release|Any CPU - {E75385A3-EA7C-4DFF-B989-BEE64BC506ED}.Test|Any CPU.ActiveCfg = Test|Any CPU - {E75385A3-EA7C-4DFF-B989-BEE64BC506ED}.Test|Any CPU.Build.0 = Test|Any CPU - {5AE358F5-4F11-47FE-94BE-1588F357DCDC}.AppVeyor|Any CPU.ActiveCfg = AppVeyor|Any CPU - {5AE358F5-4F11-47FE-94BE-1588F357DCDC}.AppVeyor|Any CPU.Build.0 = AppVeyor|Any CPU {5AE358F5-4F11-47FE-94BE-1588F357DCDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5AE358F5-4F11-47FE-94BE-1588F357DCDC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5AE358F5-4F11-47FE-94BE-1588F357DCDC}.Prod|Any CPU.ActiveCfg = Prod|Any CPU - {5AE358F5-4F11-47FE-94BE-1588F357DCDC}.Prod|Any CPU.Build.0 = Prod|Any CPU {5AE358F5-4F11-47FE-94BE-1588F357DCDC}.Release|Any CPU.ActiveCfg = Release|Any CPU {5AE358F5-4F11-47FE-94BE-1588F357DCDC}.Release|Any CPU.Build.0 = Release|Any CPU - {5AE358F5-4F11-47FE-94BE-1588F357DCDC}.Test|Any CPU.ActiveCfg = Test|Any CPU - {5AE358F5-4F11-47FE-94BE-1588F357DCDC}.Test|Any CPU.Build.0 = Test|Any CPU - {73EA6CFE-4B6A-4F83-BA3F-93F83A08E73A}.AppVeyor|Any CPU.ActiveCfg = AppVeyor|Any CPU - {73EA6CFE-4B6A-4F83-BA3F-93F83A08E73A}.AppVeyor|Any CPU.Build.0 = AppVeyor|Any CPU {73EA6CFE-4B6A-4F83-BA3F-93F83A08E73A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {73EA6CFE-4B6A-4F83-BA3F-93F83A08E73A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {73EA6CFE-4B6A-4F83-BA3F-93F83A08E73A}.Prod|Any CPU.ActiveCfg = Prod|Any CPU - {73EA6CFE-4B6A-4F83-BA3F-93F83A08E73A}.Prod|Any CPU.Build.0 = Prod|Any CPU {73EA6CFE-4B6A-4F83-BA3F-93F83A08E73A}.Release|Any CPU.ActiveCfg = Release|Any CPU {73EA6CFE-4B6A-4F83-BA3F-93F83A08E73A}.Release|Any CPU.Build.0 = Release|Any CPU - {73EA6CFE-4B6A-4F83-BA3F-93F83A08E73A}.Test|Any CPU.ActiveCfg = Test|Any CPU - {73EA6CFE-4B6A-4F83-BA3F-93F83A08E73A}.Test|Any CPU.Build.0 = Test|Any CPU - {46D252DC-0BC6-4EA6-B1B8-3896B9F00471}.AppVeyor|Any CPU.ActiveCfg = Test|Any CPU - {46D252DC-0BC6-4EA6-B1B8-3896B9F00471}.AppVeyor|Any CPU.Build.0 = Test|Any CPU {46D252DC-0BC6-4EA6-B1B8-3896B9F00471}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {46D252DC-0BC6-4EA6-B1B8-3896B9F00471}.Debug|Any CPU.Build.0 = Debug|Any CPU - {46D252DC-0BC6-4EA6-B1B8-3896B9F00471}.Prod|Any CPU.ActiveCfg = Prod|Any CPU - {46D252DC-0BC6-4EA6-B1B8-3896B9F00471}.Prod|Any CPU.Build.0 = Prod|Any CPU {46D252DC-0BC6-4EA6-B1B8-3896B9F00471}.Release|Any CPU.ActiveCfg = Release|Any CPU {46D252DC-0BC6-4EA6-B1B8-3896B9F00471}.Release|Any CPU.Build.0 = Release|Any CPU - {46D252DC-0BC6-4EA6-B1B8-3896B9F00471}.Test|Any CPU.ActiveCfg = Test|Any CPU - {46D252DC-0BC6-4EA6-B1B8-3896B9F00471}.Test|Any CPU.Build.0 = Test|Any CPU - {4B2D1B6A-F8E5-46EF-BD07-BBD0D3343913}.AppVeyor|Any CPU.ActiveCfg = Release|Any CPU - {4B2D1B6A-F8E5-46EF-BD07-BBD0D3343913}.AppVeyor|Any CPU.Build.0 = Release|Any CPU {4B2D1B6A-F8E5-46EF-BD07-BBD0D3343913}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4B2D1B6A-F8E5-46EF-BD07-BBD0D3343913}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4B2D1B6A-F8E5-46EF-BD07-BBD0D3343913}.Prod|Any CPU.ActiveCfg = Release|Any CPU - {4B2D1B6A-F8E5-46EF-BD07-BBD0D3343913}.Prod|Any CPU.Build.0 = Release|Any CPU {4B2D1B6A-F8E5-46EF-BD07-BBD0D3343913}.Release|Any CPU.ActiveCfg = Release|Any CPU {4B2D1B6A-F8E5-46EF-BD07-BBD0D3343913}.Release|Any CPU.Build.0 = Release|Any CPU - {4B2D1B6A-F8E5-46EF-BD07-BBD0D3343913}.Test|Any CPU.ActiveCfg = Release|Any CPU - {4B2D1B6A-F8E5-46EF-BD07-BBD0D3343913}.Test|Any CPU.Build.0 = Release|Any CPU - {46D4551A-7301-456B-A9BA-1E0621C09112}.AppVeyor|Any CPU.ActiveCfg = Release|Any CPU - {46D4551A-7301-456B-A9BA-1E0621C09112}.AppVeyor|Any CPU.Build.0 = Release|Any CPU - {46D4551A-7301-456B-A9BA-1E0621C09112}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {46D4551A-7301-456B-A9BA-1E0621C09112}.Debug|Any CPU.Build.0 = Debug|Any CPU - {46D4551A-7301-456B-A9BA-1E0621C09112}.Prod|Any CPU.ActiveCfg = Release|Any CPU - {46D4551A-7301-456B-A9BA-1E0621C09112}.Prod|Any CPU.Build.0 = Release|Any CPU - {46D4551A-7301-456B-A9BA-1E0621C09112}.Release|Any CPU.ActiveCfg = Release|Any CPU - {46D4551A-7301-456B-A9BA-1E0621C09112}.Release|Any CPU.Build.0 = Release|Any CPU - {46D4551A-7301-456B-A9BA-1E0621C09112}.Test|Any CPU.ActiveCfg = Release|Any CPU - {46D4551A-7301-456B-A9BA-1E0621C09112}.Test|Any CPU.Build.0 = Release|Any CPU - {FE733415-7189-4EB7-A694-8D3B2BE27549}.AppVeyor|Any CPU.ActiveCfg = Release|Any CPU - {FE733415-7189-4EB7-A694-8D3B2BE27549}.AppVeyor|Any CPU.Build.0 = Release|Any CPU {FE733415-7189-4EB7-A694-8D3B2BE27549}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FE733415-7189-4EB7-A694-8D3B2BE27549}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FE733415-7189-4EB7-A694-8D3B2BE27549}.Prod|Any CPU.ActiveCfg = Release|Any CPU - {FE733415-7189-4EB7-A694-8D3B2BE27549}.Prod|Any CPU.Build.0 = Release|Any CPU {FE733415-7189-4EB7-A694-8D3B2BE27549}.Release|Any CPU.ActiveCfg = Release|Any CPU {FE733415-7189-4EB7-A694-8D3B2BE27549}.Release|Any CPU.Build.0 = Release|Any CPU - {FE733415-7189-4EB7-A694-8D3B2BE27549}.Test|Any CPU.ActiveCfg = Release|Any CPU - {FE733415-7189-4EB7-A694-8D3B2BE27549}.Test|Any CPU.Build.0 = Release|Any CPU + {46D4551A-7301-456B-A9BA-1E0621C09112}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {46D4551A-7301-456B-A9BA-1E0621C09112}.Debug|Any CPU.Build.0 = Debug|Any CPU + {46D4551A-7301-456B-A9BA-1E0621C09112}.Release|Any CPU.ActiveCfg = Release|Any CPU + {46D4551A-7301-456B-A9BA-1E0621C09112}.Release|Any CPU.Build.0 = Release|Any CPU + {0326CAE6-87A1-4D66-84AE-EB8CE0340E9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0326CAE6-87A1-4D66-84AE-EB8CE0340E9F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0326CAE6-87A1-4D66-84AE-EB8CE0340E9F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0326CAE6-87A1-4D66-84AE-EB8CE0340E9F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -223,12 +168,13 @@ Global {46D252DC-0BC6-4EA6-B1B8-3896B9F00471} = {8FD29D91-1CAC-4A14-B085-7631FA4E81DB} {4B2D1B6A-F8E5-46EF-BD07-BBD0D3343913} = {1755BABD-3C1F-497A-AB69-9D643B576506} {99BC9B21-B4D9-4344-9393-FFAE60AA0283} = {098F0368-603B-4AF3-80A4-569EAB468147} - {46D4551A-7301-456B-A9BA-1E0621C09112} = {28A9664B-E3E1-4362-8E2D-6C5A0DED0517} {8FD29D91-1CAC-4A14-B085-7631FA4E81DB} = {42678F6A-7BFE-40AF-A659-0726A06DCE29} - {28A9664B-E3E1-4362-8E2D-6C5A0DED0517} = {42678F6A-7BFE-40AF-A659-0726A06DCE29} {FE733415-7189-4EB7-A694-8D3B2BE27549} = {1755BABD-3C1F-497A-AB69-9D643B576506} + {1A926F74-9D45-4E47-AD27-967027E1C64A} = {42678F6A-7BFE-40AF-A659-0726A06DCE29} + {46D4551A-7301-456B-A9BA-1E0621C09112} = {1A926F74-9D45-4E47-AD27-967027E1C64A} + {0326CAE6-87A1-4D66-84AE-EB8CE0340E9F} = {500B4C1D-B871-49A0-94E7-BD0623101EF6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {BD2A477D-1E55-45D1-8C0C-FBF9E8216B92} + SolutionGuid = {4890FA32-C82F-4C98-AC7A-2F4EE7F21687} EndGlobalSection EndGlobal diff --git a/Kitos_LocalApiTest.sln b/Kitos_LocalApiTest.sln new file mode 100644 index 0000000000..13591d5886 --- /dev/null +++ b/Kitos_LocalApiTest.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.757 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.Integration.Presentation.Web", "Tests.Integration.Presentation.Web\Tests.Integration.Presentation.Web.csproj", "{46D4551A-7301-456B-A9BA-1E0621C09112}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {46D4551A-7301-456B-A9BA-1E0621C09112}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {46D4551A-7301-456B-A9BA-1E0621C09112}.Debug|Any CPU.Build.0 = Debug|Any CPU + {46D4551A-7301-456B-A9BA-1E0621C09112}.Release|Any CPU.ActiveCfg = Release|Any CPU + {46D4551A-7301-456B-A9BA-1E0621C09112}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E936E26C-89E8-4367-8303-73631A5D94C4} + EndGlobalSection +EndGlobal diff --git a/Presentation.Web/App_Start/AuthConfig.cs b/Presentation.Web/App_Start/AuthConfig.cs index 36be7ef786..bba8cf2b2e 100644 --- a/Presentation.Web/App_Start/AuthConfig.cs +++ b/Presentation.Web/App_Start/AuthConfig.cs @@ -4,22 +4,7 @@ public static class AuthConfig { public static void RegisterAuth() { - // To let users of this site log in using their accounts from other sites such as Microsoft, Facebook, and Twitter, - // you must update this site. For more information visit http://go.microsoft.com/fwlink/?LinkID=252166 - - //OAuthWebSecurity.RegisterMicrosoftClient( - // clientId: "", - // clientSecret: ""); - - //OAuthWebSecurity.RegisterTwitterClient( - // consumerKey: "", - // consumerSecret: ""); - - //OAuthWebSecurity.RegisterFacebookClient( - // appId: "", - // appSecret: ""); - - //OAuthWebSecurity.RegisterGoogleClient(); + } } } diff --git a/Presentation.Web/App_Start/LogConfig.cs b/Presentation.Web/App_Start/LogConfig.cs index 98f5e674cd..10abd544be 100644 --- a/Presentation.Web/App_Start/LogConfig.cs +++ b/Presentation.Web/App_Start/LogConfig.cs @@ -1,22 +1,28 @@ -using Serilog; +using System; +using Serilog; using Serilog.Events; using Serilog.Exceptions.Destructurers; using SerilogWeb.Classic; using SerilogWeb.Classic.Enrichers; -namespace Presentation.Web.App_Start +namespace Presentation.Web { public static class LogConfig { - public static void RegisterLog() + private static readonly Lazy GlobalLoggerInstance = new Lazy(ConfigureAndCreateSerilogLogger); + + public static ILogger GlobalLogger => GlobalLoggerInstance.Value; + + private static ILogger ConfigureAndCreateSerilogLogger() { ApplicationLifecycleModule.LogPostedFormData = LogPostedFormDataOption.Always; ApplicationLifecycleModule.RequestLoggingLevel = LogEventLevel.Debug; ApplicationLifecycleModule.LogRequestBody = true; ApplicationLifecycleModule.LogResponseBody = true; - Log.Logger = new LoggerConfiguration() + return new LoggerConfiguration() .ReadFrom.AppSettings() + .Enrich.FromLogContext() .Enrich.With() .Enrich.With() .Enrich.With() @@ -26,5 +32,10 @@ public static void RegisterLog() //.WriteTo.Trace() .CreateLogger(); } + + public static void RegisterLog() + { + Log.Logger = GlobalLoggerInstance.Value; + } } } \ No newline at end of file diff --git a/Presentation.Web/App_Start/NinjectWebCommon.cs b/Presentation.Web/App_Start/NinjectWebCommon.cs index 68879a61a1..4af3d1ebeb 100644 --- a/Presentation.Web/App_Start/NinjectWebCommon.cs +++ b/Presentation.Web/App_Start/NinjectWebCommon.cs @@ -2,6 +2,8 @@ using System.Web; using System.Web.Security; using Core.ApplicationServices; +using Core.ApplicationServices.Authentication; +using Core.ApplicationServices.Authorization; using Core.DomainServices; using Infrastructure.DataAccess; using Infrastructure.OpenXML; @@ -13,6 +15,12 @@ using Presentation.Web.Infrastructure; using Presentation.Web.Properties; using Hangfire; +using Infrastructure.Services.Cryptography; +using Microsoft.Owin; +using Presentation.Web.Infrastructure.Authorization; +using Presentation.Web.Infrastructure.Factories.Authentication; +using Presentation.Web.Infrastructure.Model.Authentication; +using Serilog; [assembly: WebActivatorEx.PreApplicationStartMethod(typeof(NinjectWebCommon), "Start")] [assembly: WebActivatorEx.ApplicationShutdownMethodAttribute(typeof(NinjectWebCommon), "Stop")] @@ -82,7 +90,9 @@ private static void RegisterServices(IKernel kernel) kernel.Bind().To().InRequestScope() .WithConstructorArgument("ttl", Settings.Default.ResetPasswordTTL) .WithConstructorArgument("baseUrl", Settings.Default.BaseUrl) - .WithConstructorArgument("mailSuffix", Settings.Default.MailSuffix); + .WithConstructorArgument("mailSuffix", Settings.Default.MailSuffix) + .WithConstructorArgument("defaultUserPassword", Settings.Default.DefaultUserPassword) + .WithConstructorArgument("useDefaultUserPassword", bool.Parse(Settings.Default.UseDefaultPassword)); kernel.Bind().To().InRequestScope(); kernel.Bind().To().InRequestScope(); kernel.Bind().To().InRequestScope(); @@ -97,13 +107,57 @@ private static void RegisterServices(IKernel kernel) kernel.Bind().To().InSingletonScope(); kernel.Bind().To().InRequestScope(); kernel.Bind().To().InRequestScope().Intercept().With(new LogInterceptor()); - kernel.Bind().To().InSingletonScope(); + kernel.Bind().To().InRequestScope(); //MembershipProvider & Roleprovider injection - see ProviderInitializationHttpModule.cs kernel.Bind().ToMethod(ctx => Membership.Provider); kernel.Bind().ToMethod(ctx => Roles.Provider); + + kernel.Bind().ToConstant(LogConfig.GlobalLogger).InTransientScope(); kernel.Bind().To(); + + kernel.Bind().ToMethod(_ => HttpContext.Current.GetOwinContext()).InRequestScope(); + RegisterAuthenticationContext(kernel); + RegisterAccessContext(kernel); + } + + private static void RegisterAuthenticationContext(IKernel kernel) + { + kernel.Bind().To().InRequestScope(); + kernel.Bind().ToMethod(ctx => ctx.Kernel.Get().Create()) + .InRequestScope(); + } + + private static void RegisterAccessContext(IKernel kernel) + { + //User context + kernel.Bind().To().InRequestScope(); + kernel.Bind() + .ToMethod(ctx => + { + var factory = ctx.Kernel.Get(); + var authentication = ctx.Kernel.Get(); + bool canCreateContext = authentication.Method != AuthenticationMethod.Anonymous && authentication.ActiveOrganizationId.HasValue; + + if (canCreateContext) + { + return factory.Create(authentication.UserId.GetValueOrDefault(), authentication.ActiveOrganizationId.GetValueOrDefault()); + } + + return new UnauthenticatedUserContext(); + }) + .InRequestScope(); + + //Authorization context + kernel.Bind().To().InRequestScope(); + kernel.Bind() + .ToMethod(ctx => + { + var context = ctx.Kernel.Get(); + return ctx.Kernel.Get().Create(context); + }) + .InRequestScope(); } } } diff --git a/Presentation.Web/App_Start/SwaggerConfig.cs b/Presentation.Web/App_Start/SwaggerConfig.cs new file mode 100644 index 0000000000..c3a144da3f --- /dev/null +++ b/Presentation.Web/App_Start/SwaggerConfig.cs @@ -0,0 +1,66 @@ +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Web.Http; +using Presentation.Web; +using Presentation.Web.Swagger; +using Swashbuckle.Application; +using Swashbuckle.OData; +using WebActivatorEx; + +[assembly: PreApplicationStartMethod(typeof(SwaggerConfig), "Register")] + +namespace Presentation.Web +{ + public class SwaggerConfig + { + public static void Register() + { + GlobalConfiguration.Configuration.EnableSwagger(c => + { + c.SingleApiVersion("1.0.0", "OS2Kitos API") + .Description("Denne dokumentation udstiller de forskellige kald der kan laves til api'et i kitos. \n" + + "Mange kald bliver oprettet gennem en generisk kontroller, og disse vil ikke blive beskrevet individuelt, men blive påskrevet en værdi fra denne generiske kontroller. \n \n" + + "Til information er det ikke alle parametre der skal bruges når API'et tilgås ObjectOwnerId, LastChanged og LastChangedByUserId bliver som udgangspunkt sat af systemet automatisk. \n \n" + + "I første version af APIet er der udelukkende adgang til læseoperationer. Ved behov for adgang til funktionalitet, der ændrer i data, kontakt da venligst KITOS sekretariatet."); + + c.DocumentFilter(); + c.DocumentFilter(); + + c.GroupActionsBy(apiDesc => + { + if (apiDesc.RelativePath.Contains("api")) + { + return "API - " + apiDesc.ActionDescriptor.ControllerDescriptor.ControllerName; + } + return "ODATA - " + apiDesc.ActionDescriptor.ControllerDescriptor.ControllerName; + } + ); + + + var baseDirectory = AppDomain.CurrentDomain.BaseDirectory; + var commentsFileName = Assembly.GetExecutingAssembly().GetName().Name + ".XML"; + var commentsFile = Path.Combine(baseDirectory, "bin", commentsFileName); + c.IncludeXmlComments(commentsFile); + + c.DescribeAllEnumsAsStrings(); + + c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First()); + + c.CustomProvider(defaultProvider => new ODataSwaggerProvider(defaultProvider, c, GlobalConfiguration.Configuration).Configure(odataConfig => + { + odataConfig.EnableSwaggerRequestCaching(); + })); + + + }) + .EnableSwaggerUi(c => + { + c.InjectJavaScript(Assembly.GetExecutingAssembly(), "Presentation.Web.Scripts.SwaggerUICustom.js"); + c.EnableApiKeySupport("Authorization", "header"); + }); + + } + } +} \ No newline at end of file diff --git a/Presentation.Web/App_Start/WebApiConfig.cs b/Presentation.Web/App_Start/WebApiConfig.cs index 39c1d7ba54..efc6764fd7 100644 --- a/Presentation.Web/App_Start/WebApiConfig.cs +++ b/Presentation.Web/App_Start/WebApiConfig.cs @@ -10,7 +10,6 @@ using Core.DomainModel.Organization; using Core.DomainModel.Reports; using Microsoft.OData.Edm; -using Presentation.Web.Controllers.API; using Presentation.Web.Controllers.OData; using Presentation.Web.Controllers.OData.LocalOptionControllers; using Core.DomainModel.LocalOptions; @@ -20,17 +19,24 @@ using Core.DomainModel.AdviceSent; using Presentation.Web.Models; using System.Linq; +using System.Web.Http.Description; +using Presentation.Web.Infrastructure.Attributes; +using Presentation.Web.Infrastructure.Odata; namespace Presentation.Web { using Controllers.OData.AttachedOptions; - using DocumentFormat.OpenXml.Wordprocessing; - + using Microsoft.OData; + using Microsoft.OData.UriParser; + using System.Collections.Generic; + using System.Web.OData.Routing.Conventions; using DataType = Core.DomainModel.ItSystem.DataType; using HelpText = Core.DomainModel.HelpText; public static class WebApiConfig { + const string ControllerSuffix = "Controller"; + public static void Register(HttpConfiguration config) { config.EnableCors(new EnableCorsAttribute("*", "*", "*")); @@ -45,260 +51,229 @@ public static void Register(HttpConfiguration config) // To avoid processing unexpected or malicious queries, use the validation settings on QueryableAttribute to validate incoming queries. // For more information, visit http://go.microsoft.com/fwlink/?LinkId=279712. //config.EnableQuerySupport(); + const string routeName = "odata"; + const string routePrefix = "odata"; - //OData - config.MapODataServiceRoute( - routeName: "odata", - routePrefix: "odata", - model: GetModel()); + var route = config.MapODataServiceRoute(routeName: routeName, routePrefix: routePrefix, configureAction: (builder => builder + .AddService(ServiceLifetime.Singleton, sp => GetModel()) + .AddService(ServiceLifetime.Singleton, sp => new StringAsEnumResolver()) + .AddService(ServiceLifetime.Singleton, sp => new CaseInsensitiveResolver()) + .AddService(ServiceLifetime.Singleton, sp => new UnqualifiedODataUriResolver()) + .AddService>(ServiceLifetime.Singleton, sp => + ODataRoutingConventions.CreateDefaultWithAttributeRouting(routeName, config)))); - config.EnableEnumPrefixFree(true); - config.EnableCaseInsensitive(true); - config.EnableUnqualifiedNameCall(true); config.Formatters.Remove(config.Formatters.XmlFormatter); config.Filters.Add(new ExceptionLogFilterAttribute()); + config.Count().Filter().OrderBy().Expand().Select().MaxTop(null); } public static IEdmModel GetModel() { + const string kitosNamespace = "Kitos"; + const string entitySetOrganizations = "Organizations"; + const string entitySetItSystems = "ItSystems"; + const string entitySetEconomyStreams = "EconomyStreams"; + var builder = new ODataConventionModelBuilder(); // BUG with EnableLowerCamelCase http://stackoverflow.com/questions/39269261/odata-complains-about-missing-id-property-when-enabling-camelcasing //builder.EnableLowerCamelCase(); var accessMod = builder.AddEnumType(typeof(AccessModifier)); - accessMod.Namespace = "Kitos"; - var orgRoles = builder.AddEnumType(typeof(OrganizationRole)); - orgRoles.Namespace = "Kitos"; - - var organizationRightEntitySetName = nameof(OrganizationRightsController).Replace("Controller", string.Empty); - var organizationRights = builder.EntitySet(organizationRightEntitySetName); - organizationRights.HasRequiredBinding(o => o.Organization, "Organizations"); - organizationRights.EntityType.HasKey(x => x.Id); - - - //builder.EntitySet("Advices"); - - var agreementElementTypes = builder.EntitySet(nameof(AgreementElementTypesController).Replace("Controller", string.Empty)); - agreementElementTypes.EntityType.HasKey(x => x.Id); + accessMod.Namespace = kitosNamespace; + var orgRoles = builder.AddEnumType(typeof(OrganizationRole)); + orgRoles.Namespace = kitosNamespace; + var objectTypes = builder.AddEnumType(typeof(ObjectType)); + objectTypes.Namespace = kitosNamespace; + var schedulings = builder.AddEnumType(typeof(Scheduling)); + schedulings.Namespace = kitosNamespace; + var optionsTypes = builder.AddEnumType(typeof(OptionType)); + optionsTypes.Namespace = kitosNamespace; - var ItContractAgreementElementTypes = builder.EntitySet("ItContractAgreementElementTypes"); - ItContractAgreementElementTypes.EntityType.HasKey(x => x.ItContract_Id).HasKey(x => x.AgreementElementType_Id); + var organizationRights = BindEntitySet(builder); + organizationRights.HasRequiredBinding(o => o.Organization, entitySetOrganizations); + BindEntitySet(builder); - //builder.EntitySet("BusinessTypes"); - //builder.EntitySet("Communications"); - //builder.EntitySet("Configs"); + var itContractAgreementElementTypes = builder.EntitySet("ItContractAgreementElementTypes"); + itContractAgreementElementTypes.EntityType.HasKey(x => x.ItContract_Id).HasKey(x => x.AgreementElementType_Id); - var itContractTemplateTypes = builder.EntitySet(nameof(ItContractTemplateTypesController).Replace("Controller", string.Empty)); - itContractTemplateTypes.EntityType.HasKey(x => x.Id); + BindEntitySet(builder); - var itContractTypes = builder.EntitySet(nameof(ItContractTypesController).Replace("Controller", string.Empty)); - itContractTypes.EntityType.HasKey(x => x.Id); + BindEntitySet(builder); var dataRowUsage = builder.EntitySet("DataRowUsages"); dataRowUsage.EntityType.HasKey(x => new { x.DataRowId, x.ItSystemUsageId, x.ItSystemId, x.ItInterfaceId }); - //builder.EntitySet("EconomyYears"); - - var economyStream = builder.EntitySet("EconomyStreams"); + var economyStream = builder.EntitySet(entitySetEconomyStreams); economyStream.EntityType.HasKey(x => x.Id); var economyFunc = builder.Function("ExternEconomyStreams"); economyFunc.Parameter("Organization"); - economyFunc.ReturnsCollectionFromEntitySet("EconomyStreams"); - - var frequencyTypes = builder.EntitySet(nameof(FrequencyTypesController).Replace("Controller", string.Empty)); - frequencyTypes.EntityType.HasKey(x => x.Id); + economyFunc.ReturnsCollectionFromEntitySet(entitySetEconomyStreams); - //builder.EntitySet("Goals"); - //builder.EntitySet("GoalStatus"); - var goalTypes = builder.EntitySet(nameof(GoalTypesController).Replace("Controller", string.Empty)); - goalTypes.EntityType.HasKey(x => x.Id); - //builder.EntitySet("Handovers"); - //builder.EntitySet("HandoverTrials"); + BindEntitySet(builder); - var handoverTrialTypes = builder.EntitySet(nameof(HandoverTrialTypesController).Replace("Controller", string.Empty)); - handoverTrialTypes.EntityType.HasKey(x => x.Id); + BindEntitySet(builder); - //builder.EntitySet("Interfaces"); - //builder.EntitySet("ItInterfaceExhibits"); - //builder.EntitySet("ItInterfaceExhibtUsages"); - //builder.EntitySet("ItSystemUsageOrgUnitUsages"); + BindEntitySet(builder); - var itContractRights = builder.EntitySet(nameof(ItContractRightsController).Replace("Controller", string.Empty)); - itContractRights.EntityType.HasKey(x => x.Id); + BindEntitySet(builder); - var itContractRoles = builder.EntitySet(nameof(ItContractRolesController).Replace("Controller", string.Empty)); - itContractRoles.EntityType.HasKey(x => x.Id); + BindEntitySet(builder); - //builder.EntitySet("ItProjectStatuses"); + BindEntitySet(builder); - var itProjectRights = builder.EntitySet(nameof(ItProjectRightsController).Replace("Controller", string.Empty)); - itProjectRights.EntityType.HasKey(x => x.Id); + BindEntitySet(builder); - var itProjectRoles = builder.EntitySet(nameof(ItProjectRolesController).Replace("Controller", string.Empty)); - itProjectRoles.EntityType.HasKey(x => x.Id); - - var AttachedOptions = builder.EntitySet(nameof(AttachedOptionsController).Replace("Controller", string.Empty)); - AttachedOptions.EntityType.HasKey(x => x.Id); + BindEntitySet(builder); var itProjectOrgUnitUsage = builder.EntitySet("ItProjectOrgUnitUsages"); // no controller yet itProjectOrgUnitUsage.EntityType.HasKey(x => new { x.ItProjectId, x.OrganizationUnitId }); - var itProject = builder.EntitySet(nameof(ItProjectsController).Replace("Controller", string.Empty)); - itProject.HasRequiredBinding(o => o.Organization, "Organizations"); + var itProject = builder.EntitySet(nameof(ItProjectsController).Replace(ControllerSuffix, string.Empty)); + itProject.HasRequiredBinding(o => o.Organization, entitySetOrganizations); itProject.EntityType.HasKey(x => x.Id); var interfaceUsage = builder.EntitySet("ItInterfaceUsages"); // no controller yet interfaceUsage.EntityType.HasKey(x => new { x.ItSystemUsageId, x.ItSystemId, x.ItInterfaceId }); - var dataOption = builder.EntitySet(nameof(DataTypesController).Replace("Controller", string.Empty)); - dataOption.EntityType.HasKey(x => x.Id); + BindEntitySet(builder); var dataRow = builder.EntitySet("DataRows"); // no controller yet dataRow.EntityType.HasKey(x => x.Id); - var archiveLocation = builder.EntitySet(nameof(ArchiveLocationsController).Replace("Controller", string.Empty)); - archiveLocation.EntityType.HasKey(x => x.Id); + BindEntitySet(builder); - var archiveTestLocation = builder.EntitySet(nameof(ArchiveTestLocationsController).Replace("Controller", string.Empty)); - archiveTestLocation.EntityType.HasKey(x => x.Id); + BindEntitySet(builder); - var archiveOption = builder.EntitySet(nameof(ArchiveTypesController).Replace("Controller", string.Empty)); - archiveOption.EntityType.HasKey(x => x.Id); + BindEntitySet(builder); - var itSystemCategories = builder.EntitySet(nameof(ItSystemCategoriesController).Replace("Controller", string.Empty)); - itSystemCategories.EntityType.HasKey(x => x.Id); + BindEntitySet(builder); - var itSystems = builder.EntitySet(nameof(ItSystemsController).Replace("Controller", string.Empty)); - itSystems.HasRequiredBinding(o => o.Organization, "Organizations"); - itSystems.HasRequiredBinding(o => o.BelongsTo, "Organizations"); - itSystems.HasManyBinding(i => i.Children, "ItSystems"); - itSystems.HasRequiredBinding(i => i.Parent, "ItSystems"); - itSystems.EntityType.HasKey(x => x.Id); + var itSystems = BindEntitySet(builder); + itSystems.HasRequiredBinding(o => o.Organization, entitySetOrganizations); + itSystems.HasRequiredBinding(o => o.BelongsTo, entitySetOrganizations); + itSystems.HasManyBinding(i => i.Children, entitySetItSystems); + itSystems.HasRequiredBinding(i => i.Parent, entitySetItSystems); - var itSystemType = builder.EntitySet(nameof(ItSystemTypesController).Replace("Controller", string.Empty)); - itSystemType.HasManyBinding(i => i.References, "ItSystems"); - itSystemType.EntityType.HasKey(x => x.Id); + var itSystemType = BindEntitySet(builder); + itSystemType.HasManyBinding(i => i.References, entitySetItSystems); - var businessTypes = builder.EntitySet(nameof(BusinessTypesController).Replace("Controller", string.Empty)); - businessTypes.EntityType.HasKey(x => x.Id); - businessTypes.HasManyBinding(b => b.References, "ItSystems"); + var businessTypes = BindEntitySet(builder); + businessTypes.HasManyBinding(b => b.References, entitySetItSystems); var taskRefs = builder.EntitySet("TaskRefs"); // no controller yet - taskRefs.HasManyBinding(t => t.ItSystems, "ItSystems"); + taskRefs.HasManyBinding(t => t.ItSystems, entitySetItSystems); taskRefs.EntityType.HasKey(x => x.Id); - - var ReportsMunicipalitiesEntitySetName = nameof(ReportsMunicipalitiesController).Replace("Controller", string.Empty); - var ReportsMunicipalities = builder.EntitySet(ReportsMunicipalitiesEntitySetName); - ReportsMunicipalities.HasManyBinding(o => o.ItSystems, "ItSystems"); - ReportsMunicipalities.HasManyBinding(o => o.BelongingSystems, "ItSystems"); - - var ReportsItSystemsEntitySetName = nameof(ReportsItSystemsController).Replace("Controller", string.Empty); - var ReportsItSystems = builder.EntitySet(ReportsItSystemsEntitySetName); - ReportsItSystems.HasRequiredBinding(o => o.Organization, "Organizations"); - ReportsItSystems.HasRequiredBinding(o => o.BelongsTo, "Organizations"); - ReportsItSystems.HasManyBinding(i => i.Children, "ItSystems"); - ReportsItSystems.HasRequiredBinding(i => i.Parent, "ItSystems"); - //singleton instead of entity type because of navigation conflict with 'ItSystemRoles' - var ReportsItSystemRolesEntitySetName = nameof(ReportsItSystemRolesController).Replace("Controller", string.Empty); - var ReportsItSystemRoles = builder.EntitySet(ReportsItSystemRolesEntitySetName); + var reportsMunicipalities = BindEntitySet(builder); + reportsMunicipalities.HasManyBinding(o => o.ItSystems, entitySetItSystems); + reportsMunicipalities.HasManyBinding(o => o.BelongingSystems, entitySetItSystems); + + var reportsItSystems = BindEntitySet(builder); + reportsItSystems.HasRequiredBinding(o => o.Organization, entitySetOrganizations); + reportsItSystems.HasRequiredBinding(o => o.BelongsTo, entitySetOrganizations); + reportsItSystems.HasManyBinding(i => i.Children, entitySetItSystems); + reportsItSystems.HasRequiredBinding(i => i.Parent, entitySetItSystems); - + //singleton instead of entity type because of navigation conflict with 'ItSystemRoles' + BindEntitySet(builder); //singleton instead of entity type because of navigation conflict with 'ItSystemRights' - var ReportsITSystemContactsEntitySetName = nameof(ReportsITSystemContactsController).Replace("Controller", string.Empty); - var ReportsITSystemContacts = builder.EntitySet(ReportsITSystemContactsEntitySetName); + var ReportsITSystemContacts = BindTypeSet(builder); ReportsITSystemContacts.EntityType.HasKey(x => x.roleId); - var organizationEntitySetName = nameof(OrganizationsController).Replace("Controller", string.Empty); - var organizations = builder.EntitySet(organizationEntitySetName); - organizations.EntityType.HasKey(x => x.Id); + + var orgNameSpace = entitySetOrganizations; + + var organizations = BindEntitySet(builder); organizations.EntityType.HasMany(x => x.OrgUnits).IsNavigable().Name = "OrganizationUnits"; - organizations.HasManyBinding(o => o.ItSystems, "ItSystems"); - organizations.HasManyBinding(o => o.BelongingSystems, "ItSystems"); + organizations.EntityType.Property(p => p.Uuid).IsOptional(); - var adviceFunction = organizations.EntityType.Function("Advice").ReturnsCollectionFromEntitySet("Advice"); - var removeUserAction = organizations.EntityType.Action("RemoveUser"); + organizations.HasManyBinding(o => o.ItSystems, entitySetItSystems); + organizations.HasManyBinding(o => o.BelongingSystems, entitySetItSystems); + + var removeUserAction = organizations.EntityType.Collection.Action("RemoveUser"); + removeUserAction.Parameter("orgKey").OptionalParameter = false; removeUserAction.Parameter("userId").OptionalParameter = false; + removeUserAction.Namespace = orgNameSpace; - var userFunction = organizations.EntityType.Function("Users").ReturnsCollectionFromEntitySet("Users"); + var getAdviceByOrgFunction = organizations.EntityType.Collection.Function("GetByOrganization").ReturnsCollectionFromEntitySet("Advice"); + getAdviceByOrgFunction.Parameter("userId").OptionalParameter = false; + getAdviceByOrgFunction.ReturnsCollectionFromEntitySet(nameof(Controllers.OData.AdviceController).Replace(ControllerSuffix, string.Empty)); + getAdviceByOrgFunction.Namespace = orgNameSpace; - var orgUnits = builder.EntitySet(nameof(OrganizationUnitsController).Replace("Controller", string.Empty)); - orgUnits.HasRequiredBinding(o => o.Organization, "Organizations"); + var orgUnits = builder.EntitySet(nameof(OrganizationUnitsController).Replace(ControllerSuffix, string.Empty)); + orgUnits.HasRequiredBinding(o => o.Organization, entitySetOrganizations); orgUnits.EntityType.HasKey(x => x.Id); orgUnits.EntityType.HasMany(x => x.ResponsibleForItContracts).Name = "ItContracts"; orgUnits.EntityType.HasMany(x => x.UsingItProjects).Name = "ItProjects"; //Add isActive to result form odata - builder.StructuralTypes.First(t => t.ClrType == typeof(ItContract)).AddProperty(typeof(ItContract).GetProperty("IsActive")); - + builder.StructuralTypes.First(t => t.ClrType == typeof(ItContract)).AddProperty(typeof(ItContract).GetProperty(nameof(ItContract.IsActive))); - var userEntitySetName = nameof(UsersController).Replace("Controller", string.Empty); + var userNameSpace = "Users"; + var userEntitySetName = nameof(UsersController).Replace(ControllerSuffix, string.Empty); var users = builder.EntitySet(userEntitySetName); - users.HasRequiredBinding(u => u.DefaultOrganization, "Organizations"); + users.HasRequiredBinding(u => u.DefaultOrganization, entitySetOrganizations); users.EntityType.HasKey(x => x.Id); users.EntityType.Ignore(x => x.Password); users.EntityType.Ignore(x => x.Salt); users.EntityType.Property(x => x.Name).IsRequired(); users.EntityType.Property(x => x.Email).IsRequired(); - var userCreateAction = users.EntityType.Collection.Action("Create").ReturnsFromEntitySet(userEntitySetName); + var orgGetUsersFunction = organizations.EntityType.Function("GetUsers").ReturnsCollectionFromEntitySet(userEntitySetName); + orgGetUsersFunction.Namespace = orgNameSpace; + var userCreateAction = users.EntityType.Collection.Action("Create").ReturnsFromEntitySet(userEntitySetName); + userCreateAction.Namespace = userNameSpace; userCreateAction.Parameter("user").OptionalParameter = false; userCreateAction.Parameter("organizationId").OptionalParameter = false; userCreateAction.Parameter("sendMailOnCreation").OptionalParameter = true; var userCheckEmailFunction = users.EntityType.Collection.Function("IsEmailAvailable").Returns(); userCheckEmailFunction.Parameter("email").OptionalParameter = false; + userCheckEmailFunction.Namespace = userNameSpace; var userGetByMailFunction = builder.Function("GetUserByEmail").ReturnsFromEntitySet(userEntitySetName); userGetByMailFunction.Parameter("email").OptionalParameter = false; - var usages = builder.EntitySet(nameof(ItSystemUsagesController).Replace("Controller", string.Empty)); - usages.HasRequiredBinding(u => u.Organization, "Organizations"); - usages.HasRequiredBinding(u => u.ItSystem, "ItSystems"); - usages.EntityType.HasKey(x => x.Id); + var usages = BindEntitySet(builder); + usages.HasRequiredBinding(u => u.Organization, entitySetOrganizations); + usages.HasRequiredBinding(u => u.ItSystem, entitySetItSystems); - var itSystemRights = builder.EntitySet(nameof(ItSystemRightsController).Replace("Controller", string.Empty)); + var itSystemRights = BindEntitySet(builder); itSystemRights.HasRequiredBinding(u => u.Role, "ItSystemRoles"); - itSystemRights.EntityType.HasKey(x => x.Id); - var roles = builder.EntitySet(nameof(ItSystemRolesController).Replace("Controller", string.Empty)); - roles.EntityType.HasKey(x => x.Id); + BindEntitySet(builder); var systemOrgUnitUsages = builder.EntitySet("ItSystemUsageOrgUnitUsages"); // no controller yet systemOrgUnitUsages.EntityType.HasKey(x => x.ItSystemUsageId).HasKey(x => x.OrganizationUnitId); var contractItSystemUsages = builder.EntitySet("ItContractItSystemUsages"); // no controller yet contractItSystemUsages.EntityType.HasKey(x => x.ItContractId).HasKey(x => x.ItSystemUsageId); - builder.StructuralTypes.First(t => t.ClrType == typeof(ItSystemUsage)).AddProperty(typeof(ItSystemUsage).GetProperty("IsActive")); + builder.StructuralTypes.First(t => t.ClrType == typeof(ItSystemUsage)).AddProperty(typeof(ItSystemUsage).GetProperty(nameof(ItSystemUsage.IsActive))); - var contracts = builder.EntitySet(nameof(ItContractsController).Replace("Controller", string.Empty)); - contracts.HasRequiredBinding(o => o.Organization, "Organizations"); - contracts.HasRequiredBinding(o => o.Supplier, "Organizations"); - contracts.EntityType.HasKey(x => x.Id); + var contracts = BindEntitySet(builder); + contracts.HasRequiredBinding(o => o.Organization, entitySetOrganizations); + contracts.HasRequiredBinding(o => o.Supplier, entitySetOrganizations); contracts.EntityType.HasMany(x => x.ExternEconomyStreams).IsNotExpandable(); // do not remove contracts.EntityType.HasMany(x => x.InternEconomyStreams).IsNotExpandable(); // do not remove // TODO this field is causing issues. // This query fails: /odata/Organizations(1)/ItSystemUsages?$expand=MainContract($expand=ItContract) - var interfaceTypes = builder.EntitySet(nameof(InterfaceTypesController).Replace("Controller", string.Empty)); - interfaceTypes.EntityType.HasKey(x => x.Id); + BindEntitySet(builder); - var itInterfaces = builder.EntitySet(nameof(ItInterfacesController).Replace("Controller", string.Empty)); - itInterfaces.HasRequiredBinding(o => o.Organization, "Organizations"); - itInterfaces.HasRequiredBinding(o => o.BelongsTo, "Organizations"); - itInterfaces.EntityType.HasKey(x => x.Id); + var itInterfaces = BindEntitySet(builder); + itInterfaces.HasRequiredBinding(o => o.Organization, entitySetOrganizations); + itInterfaces.HasRequiredBinding(o => o.BelongsTo, entitySetOrganizations); - var itInterfaceTypes = builder.EntitySet(nameof(ItInterfaceTypesController).Replace("Controller", string.Empty)); - itInterfaceTypes.EntityType.HasKey(x => x.Id); + BindEntitySet(builder); var itInterfaceExihibits = builder.EntitySet("ItInterfaceExhibits"); // no controller yet - itInterfaceExihibits.HasRequiredBinding(o => o.ItSystem, "ItSystems"); + itInterfaceExihibits.HasRequiredBinding(o => o.ItSystem, entitySetItSystems); itInterfaceExihibits.EntityType.HasKey(x => x.Id); var itInterfaceExhibitUsage = builder.EntitySet("ItInterfaceExhibitUsages"); // no controller yet @@ -306,54 +281,28 @@ public static IEdmModel GetModel() .HasKey(x => x.ItInterfaceExhibitId) .HasKey(x => x.ItSystemUsageId); - // Udkommenteret ifm. OS2KITOS-663 - //var itInterfaceUse = builder.EntitySet(nameof(ItInterfaceUsesEntityController).Replace("Controller", string.Empty)); - //itInterfaceUse.EntityType - // .HasKey(x => x.ItSystemId) - // .HasKey(x => x.ItInterfaceId); + BindEntitySet(builder); - var tsas = builder.EntitySet(nameof(TsaTypesController).Replace("Controller", string.Empty)); - tsas.EntityType.HasKey(x => x.Id); + BindEntitySet(builder); - var methodTypes = builder.EntitySet(nameof(MethodTypesController).Replace("Controller", string.Empty)); - methodTypes.EntityType.HasKey(x => x.Id); + BindEntitySet(builder); - var sensitiveDataOption = builder.EntitySet(nameof(SensitiveDataTypesController).Replace("Controller", string.Empty)); - sensitiveDataOption.EntityType.HasKey(x => x.Id); + BindEntitySet(builder); - var RegularPersonalDataTypes = builder.EntitySet(nameof(RegularPersonalDataTypesController).Replace("Controller", string.Empty)); - RegularPersonalDataTypes.EntityType.HasKey(x => x.Id); - RegularPersonalDataTypes.HasManyBinding(b => b.References, "ItSystems"); + var sensitivePersonalDataTypes = BindEntitySet(builder); + sensitivePersonalDataTypes.HasManyBinding(b => b.References, entitySetItSystems); - var RegisterTypes = builder.EntitySet(nameof(RegisterTypesController).Replace("Controller", string.Empty)); - RegisterTypes.EntityType.HasKey(x => x.Id); + BindEntitySet(builder); - var SensitivePersonalDataTypes = builder.EntitySet(nameof(SensistivePersonalDataTypesController).Replace("Controller", string.Empty)); - SensitivePersonalDataTypes.EntityType.HasKey(x => x.Id); - SensitivePersonalDataTypes.HasManyBinding(b => b.References, "ItSystems"); + BindEntitySet(builder); - var optionExtendTypes = builder.EntitySet(nameof(OptionExtendTypesController).Replace("Controller", string.Empty)); - optionExtendTypes.EntityType.HasKey(x => x.Id); + BindEntitySet(builder); - var organizationUnitRights = builder.EntitySet(nameof(OrganizationUnitRightsController).Replace("Controller", string.Empty)); - organizationUnitRights.EntityType.HasKey(x => x.Id); + BindEntitySet(builder); - var organiationUnitRoles = builder.EntitySet(nameof(OrganizationUnitRolesController).Replace("Controller", string.Empty)); - organiationUnitRoles.EntityType.HasKey(x => x.Id); + BindEntitySet(builder); - //builder.EntitySet("PasswordResetRequests"); - - var paymentFreqencyTypes = builder.EntitySet(nameof(PaymentFrequencyTypesController).Replace("Controller", string.Empty)); - paymentFreqencyTypes.EntityType.HasKey(x => x.Id); - - //builder.EntitySet("PaymentMilestones"); - //builder.EntitySet("PaymentModelTypes"); - - var paymentModelTypes = builder.EntitySet(nameof(PaymentModelTypesController).Replace("Controller", string.Empty)); - paymentModelTypes.EntityType.HasKey(x => x.Id); - - var priceRegulationTypes = builder.EntitySet(nameof(PriceRegulationTypesController).Replace("Controller", string.Empty)); - priceRegulationTypes.EntityType.HasKey(x => x.Id); + BindEntitySet(builder); // These two lines causes an 404 error when requesting odata/ProcurementStrategyTypes at https://localhost:44300/#/global-config/contract // Requesting api/ProcurementStrategy works but not odata/ProcurementStrategyTypes @@ -362,238 +311,202 @@ public static IEdmModel GetModel() // There two lines fixes the 404 error at https://localhost:44300/#/global-config/contract // Requesting api/ProcurementStrategy and odata/ProcurementStrategyTypes both work - var procurementStrategyTypes = builder.EntitySet(nameof(ProcurementStrategyTypesController).Replace("Controller", string.Empty)); - procurementStrategyTypes.EntityType.HasKey(x => x.Id); + BindEntitySet(builder); - var itProjectTypes = builder.EntitySet(nameof(ItProjectTypesController).Replace("Controller", string.Empty)); - itProjectTypes.EntityType.HasKey(x => x.Id); + BindEntitySet(builder); - var purchaseFormType = builder.EntitySet(nameof(PurchaseFormTypesController).Replace("Controller", string.Empty)); - purchaseFormType.EntityType.HasKey(x => x.Id); + BindEntitySet(builder); //Local options - var LocalAgreementElementType = builder.EntitySet(nameof(LocalAgreementElementTypesController).Replace("Controller", string.Empty)); - LocalAgreementElementType.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalAgreementElementType.EntityType.HasKey(x => x.Id); + var localAgreementElementType = BindEntitySet(builder); + localAgreementElementType.HasRequiredBinding(u => u.Organization, entitySetOrganizations); + + + var localArchiveType = BindEntitySet(builder); + localArchiveType.HasRequiredBinding(u => u.Organization, entitySetOrganizations); + + var localArchiveLocation = BindEntitySet(builder); + localArchiveLocation.HasRequiredBinding(u => u.Organization, entitySetOrganizations); + + var localArchiveTestLocation = BindEntitySet (builder); + localArchiveTestLocation.HasRequiredBinding(u => u.Organization, entitySetOrganizations); + + var localItSystemCategories = BindEntitySet (builder); + localItSystemCategories.HasRequiredBinding(x => x.Organization, entitySetOrganizations); + + var localBusinessType = BindEntitySet (builder); + localBusinessType.HasRequiredBinding(u => u.Organization, entitySetOrganizations); + + var localDataType = BindEntitySet (builder); + localDataType.HasRequiredBinding(u => u.Organization, entitySetOrganizations); + + var localFrequencyType = BindEntitySet (builder); + localFrequencyType.HasRequiredBinding(u => u.Organization, entitySetOrganizations); + + var localGoalType = BindEntitySet (builder); + localGoalType.HasRequiredBinding(u => u.Organization, entitySetOrganizations); + + var localHandoverTrialType = BindEntitySet (builder); + localHandoverTrialType.HasRequiredBinding(u => u.Organization, entitySetOrganizations); + + var localInterfaceType = BindEntitySet (builder); + localInterfaceType.HasRequiredBinding(u => u.Organization, entitySetOrganizations); + + var localItContractRole = BindEntitySet (builder); + localItContractRole.HasRequiredBinding(u => u.Organization, entitySetOrganizations); + + var localItContractTemplateType = BindEntitySet (builder); + localItContractTemplateType.HasRequiredBinding(u => u.Organization, entitySetOrganizations); + + var localItContractType = BindEntitySet (builder); + localItContractType.HasRequiredBinding(u => u.Organization, entitySetOrganizations); + var localItInterfaceType = BindEntitySet (builder); + localItInterfaceType.HasRequiredBinding(u => u.Organization, entitySetOrganizations); - var LocalArchiveType = builder.EntitySet(nameof(LocalArchiveTypesController).Replace("Controller", string.Empty)); - LocalArchiveType.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalArchiveType.EntityType.HasKey(x => x.Id); + var localItProjectRole = BindEntitySet (builder); + localItProjectRole.HasRequiredBinding(u => u.Organization, entitySetOrganizations); - var LocalArchiveLocation = builder.EntitySet(nameof(LocalArchiveLocationsController).Replace("Controller", string.Empty)); - LocalArchiveLocation.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalArchiveLocation.EntityType.HasKey(x => x.Id); + var localItProjectType = BindEntitySet (builder); + localItProjectType.HasRequiredBinding(u => u.Organization, entitySetOrganizations); - var LocalArchiveTestLocation = builder.EntitySet(nameof(LocalArchiveTestLocationsController).Replace("Controller", string.Empty)); - LocalArchiveTestLocation.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalArchiveTestLocation.EntityType.HasKey(x => x.Id); + var localItSystemRole = BindEntitySet (builder); + localItSystemRole.HasRequiredBinding(u => u.Organization, entitySetOrganizations); - var LocalItSystemCategories = builder.EntitySet(nameof(LocalItSystemCategoriesController).Replace("Controller", string.Empty)); - LocalItSystemCategories.HasRequiredBinding(x => x.Organization, "Organizations"); - LocalItSystemCategories.EntityType.HasKey(x => x.Id); + var localItSystemType = BindEntitySet (builder); + localItSystemType.HasRequiredBinding(u => u.Organization, entitySetOrganizations); - var LocalBusinessType = builder.EntitySet(nameof(LocalBusinessTypesController).Replace("Controller", string.Empty)); - LocalBusinessType.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalBusinessType.EntityType.HasKey(x => x.Id); - - var LocalDataType = builder.EntitySet(nameof(LocalDataTypesController).Replace("Controller", string.Empty)); - LocalDataType.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalDataType.EntityType.HasKey(x => x.Id); - - var LocalFrequencyType = builder.EntitySet(nameof(LocalFrequencyTypesController).Replace("Controller", string.Empty)); - LocalFrequencyType.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalFrequencyType.EntityType.HasKey(x => x.Id); - - var LocalGoalType = builder.EntitySet(nameof(LocalGoalTypesController).Replace("Controller", string.Empty)); - LocalGoalType.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalGoalType.EntityType.HasKey(x => x.Id); - - var LocalHandoverTrialType = builder.EntitySet(nameof(LocalHandoverTrialTypesController).Replace("Controller", string.Empty)); - LocalHandoverTrialType.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalHandoverTrialType.EntityType.HasKey(x => x.Id); - - var LocalInterfaceType = builder.EntitySet(nameof(LocalInterfaceTypesController).Replace("Controller", string.Empty)); - LocalInterfaceType.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalInterfaceType.EntityType.HasKey(x => x.Id); - - var LocalItContractRole = builder.EntitySet(nameof(LocalItContractRolesController).Replace("Controller", string.Empty)); - LocalItContractRole.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalItContractRole.EntityType.HasKey(x => x.Id); - - var LocalItContractTemplateType = builder.EntitySet(nameof(LocalItContractTemplateTypesController).Replace("Controller", string.Empty)); - LocalItContractTemplateType.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalItContractTemplateType.EntityType.HasKey(x => x.Id); - - var LocalItContractType = builder.EntitySet(nameof(LocalItContractTypesController).Replace("Controller", string.Empty)); - LocalItContractType.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalItContractType.EntityType.HasKey(x => x.Id); - - var LocalItInterfaceType = builder.EntitySet(nameof(LocalItInterfaceTypesController).Replace("Controller", string.Empty)); - LocalItInterfaceType.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalItInterfaceType.EntityType.HasKey(x => x.Id); - - var LocalItProjectRole = builder.EntitySet(nameof(LocalItProjectRolesController).Replace("Controller", string.Empty)); - LocalItProjectRole.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalItProjectRole.EntityType.HasKey(x => x.Id); - - var LocalItProjectType = builder.EntitySet(nameof(LocalItProjectTypesController).Replace("Controller", string.Empty)); - LocalItProjectType.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalItProjectType.EntityType.HasKey(x => x.Id); - - var LocalItSystemRole = builder.EntitySet(nameof(LocalItSystemRolesController).Replace("Controller", string.Empty)); - LocalItSystemRole.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalItSystemRole.EntityType.HasKey(x => x.Id); - - var LocalItSystemType = builder.EntitySet(nameof(LocalItSystemTypesController).Replace("Controller", string.Empty)); - LocalItSystemType.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalItSystemType.EntityType.HasKey(x => x.Id); - - var LocalMethodType = builder.EntitySet(nameof(LocalMethodTypesController).Replace("Controller", string.Empty)); - LocalMethodType.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalMethodType.EntityType.HasKey(x => x.Id); - - var LocalOptionExtendType = builder.EntitySet(nameof(LocalOptionExtendTypesController).Replace("Controller", string.Empty)); - LocalOptionExtendType.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalOptionExtendType.EntityType.HasKey(x => x.Id); - - var LocalPaymentFreqencyType = builder.EntitySet(nameof(LocalPaymentFrequencyTypesController).Replace("Controller", string.Empty)); - LocalPaymentFreqencyType.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalPaymentFreqencyType.EntityType.HasKey(x => x.Id); - - var LocalPaymentModelType = builder.EntitySet(nameof(LocalPaymentModelTypesController).Replace("Controller", string.Empty)); - LocalPaymentModelType.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalPaymentModelType.EntityType.HasKey(x => x.Id); - - var LocalPriceRegulationType = builder.EntitySet(nameof(LocalPriceRegulationTypesController).Replace("Controller", string.Empty)); - LocalPriceRegulationType.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalPriceRegulationType.EntityType.HasKey(x => x.Id); - - var LocalProcurementStrategyType = builder.EntitySet(nameof(LocalProcurementStrategyTypesController).Replace("Controller", string.Empty)); - LocalProcurementStrategyType.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalProcurementStrategyType.EntityType.HasKey(x => x.Id); - - var LocalPurchaseFormType = builder.EntitySet(nameof(LocalPurchaseFormTypesController).Replace("Controller", string.Empty)); - LocalPurchaseFormType.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalPurchaseFormType.EntityType.HasKey(x => x.Id); - - var LocalReportCategoryType = builder.EntitySet(nameof(LocalReportCategoryTypesController).Replace("Controller", string.Empty)); - LocalReportCategoryType.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalReportCategoryType.EntityType.HasKey(x => x.Id); - - var RemoveOption = builder.Function("RemoveOption"); - RemoveOption.Parameter("id"); - RemoveOption.Parameter("objectId"); - RemoveOption.Parameter("type"); - RemoveOption.Parameter("entityType"); - RemoveOption.Returns(); - - var GetSensitivePersonalDataByObjectID = builder.Function("GetSensitivePersonalDataByObjectID"); - GetSensitivePersonalDataByObjectID.Parameter("id"); - GetSensitivePersonalDataByObjectID.Parameter("entitytype"); - GetSensitivePersonalDataByObjectID.ReturnsCollectionFromEntitySet("SensistivePersonalDataTypes"); - builder.StructuralTypes.First(t => t.ClrType == typeof(SensitivePersonalDataType)).AddProperty(typeof(SensitivePersonalDataType).GetProperty("Checked")); - - var GetRegularPersonalDataByObjectID = builder.Function("GetRegularPersonalDataByObjectID"); - GetRegularPersonalDataByObjectID.Parameter("id"); - GetRegularPersonalDataByObjectID.Parameter("entitytype"); - GetRegularPersonalDataByObjectID.ReturnsCollectionFromEntitySet("RegularPersonalDataTypes"); - builder.StructuralTypes.First(t => t.ClrType == typeof(RegularPersonalDataType)).AddProperty(typeof(RegularPersonalDataType).GetProperty("Checked")); - - var GetRegisterTypeByObjectID = builder.Function("GetRegisterTypesByObjectID"); - GetRegisterTypeByObjectID.Parameter("id"); - GetRegisterTypeByObjectID.ReturnsCollectionFromEntitySet("RegisterTypes"); - builder.StructuralTypes.First(t => t.ClrType == typeof(RegisterType)).AddProperty(typeof(RegisterType).GetProperty("Checked")); - - var LocalSensitiveDataType = builder.EntitySet(nameof(LocalSensitiveDataTypesController).Replace("Controller", string.Empty)); - LocalSensitiveDataType.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalSensitiveDataType.EntityType.HasKey(x => x.Id); - - var LocalSensistivePersonalDataTypes = builder.EntitySet(nameof(LocalSensistivePersonalDataTypesController).Replace("Controller", string.Empty)); - LocalSensistivePersonalDataTypes.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalSensistivePersonalDataTypes.EntityType.HasKey(x => x.Id); - - var LocalRegularPersonalDataTypes = builder.EntitySet(nameof(LocalRegularPersonalDataTypesController).Replace("Controller", string.Empty)); - LocalRegularPersonalDataTypes.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalRegularPersonalDataTypes.EntityType.HasKey(x => x.Id); - - var LocalRegisterTypes = builder.EntitySet(nameof(LocalRegisterTypesController).Replace("Controller", string.Empty)); - LocalRegisterTypes.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalRegisterTypes.EntityType.HasKey(x => x.Id); - - var LocalTerminationDeadlineType = builder.EntitySet(nameof(LocalTerminationDeadlineTypesController).Replace("Controller", string.Empty)); - LocalTerminationDeadlineType.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalTerminationDeadlineType.EntityType.HasKey(x => x.Id); - - var LocalTsaType = builder.EntitySet(nameof(LocalTsaTypesController).Replace("Controller", string.Empty)); - LocalTsaType.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalTsaType.EntityType.HasKey(x => x.Id); - - var LocalOrganizationUnitRole = builder.EntitySet(nameof(LocalOrganizationUnitRolesController).Replace("Controller", string.Empty)); - LocalOrganizationUnitRole.HasRequiredBinding(u => u.Organization, "Organizations"); - LocalOrganizationUnitRole.EntityType.HasKey(x => x.Id); - - //builder.EntitySet("Risks"); - //builder.EntitySet("Stakeholders"); - - var terminationDeadlineType = builder.EntitySet(nameof(TerminationDeadlineTypesController).Replace("Controller", string.Empty)); - terminationDeadlineType.EntityType.HasKey(x => x.Id); - - var config = builder.EntitySet(nameof(ConfigsController).Replace("Controller", string.Empty)); - config.HasRequiredBinding(u => u.Organization, "Organizations"); - config.EntityType.HasKey(x => x.Id); - - - var Advice = builder.EntitySet(nameof(Controllers.OData.AdviceController).Replace("Controller", string.Empty)); - Advice.EntityType.HasKey(x => x.Id); - - var adviceSent = builder.EntitySet(nameof(AdviceSentController).Replace("Controller", string.Empty)); - adviceSent.EntityType.HasKey(x => x.Id); - // var GetByObjectId = users.EntityType.Collection.Function("GetByObjectId").Returns(); - - var GetAdvicesByObjectID = builder.Function("GetAdvicesByObjectID"); - GetAdvicesByObjectID.Parameter("id"); - GetAdvicesByObjectID.Parameter("type"); - GetAdvicesByObjectID.ReturnsCollectionFromEntitySet("Advice"); - - - var globalConfig = builder.EntitySet(nameof(GlobalConfigsController).Replace("Controller", string.Empty)); - globalConfig.EntityType.HasKey(x => x.Id); - - var accessType = builder.EntitySet(nameof(AccessTypesController).Replace("Controller", string.Empty)); - accessType.HasRequiredBinding(a => a.ItSystem, "ItSystems"); - accessType.EntityType.HasKey(x => x.Id); - - var archivePeriod = builder.EntitySet(nameof(ArchivePeriodsController).Replace("Controller", string.Empty)); - archivePeriod.EntityType.HasKey(x => x.Id); - - //builder.EntitySet("TaskRefs"); - //builder.EntitySet("TaskUsages"); - //builder.EntitySet("Texts"); - //builder.EntitySet("Users"); - //builder.EntitySet("Wishes"); + var localMethodType = BindEntitySet (builder); + localMethodType.HasRequiredBinding(u => u.Organization, entitySetOrganizations); + + var localOptionExtendType = BindEntitySet (builder); + localOptionExtendType.HasRequiredBinding(u => u.Organization, entitySetOrganizations); + + var localPaymentFreqencyType = BindEntitySet (builder); + localPaymentFreqencyType.HasRequiredBinding(u => u.Organization, entitySetOrganizations); + + var localPaymentModelType = BindEntitySet (builder); + localPaymentModelType.HasRequiredBinding(u => u.Organization, entitySetOrganizations); + + var localPriceRegulationType = BindEntitySet (builder); + localPriceRegulationType.HasRequiredBinding(u => u.Organization, entitySetOrganizations); + + var localProcurementStrategyType = BindEntitySet (builder); + localProcurementStrategyType.HasRequiredBinding(u => u.Organization, entitySetOrganizations); + + var localPurchaseFormType = BindEntitySet (builder); + localPurchaseFormType.HasRequiredBinding(u => u.Organization, entitySetOrganizations); + + var localReportCategoryType = BindEntitySet (builder); + localReportCategoryType.HasRequiredBinding(u => u.Organization, entitySetOrganizations); + + var removeOption = builder.Function("RemoveOption"); + removeOption.Parameter("id"); + removeOption.Parameter("objectId"); + removeOption.Parameter("type"); + removeOption.Parameter("entityType"); + removeOption.Returns(); + + var getSensitivePersonalDataByUsageId = builder.Function("GetSensitivePersonalDataByUsageId"); + getSensitivePersonalDataByUsageId.Parameter("id"); + getSensitivePersonalDataByUsageId.ReturnsCollectionFromEntitySet("SensistivePersonalDataTypes"); + builder.StructuralTypes.First(t => t.ClrType == typeof(SensitivePersonalDataType)).AddProperty(typeof(SensitivePersonalDataType).GetProperty(nameof(SensitivePersonalDataType.Checked))); + getSensitivePersonalDataByUsageId.Namespace = "gdpr"; + + var getSensitivePersonalDataBySystemId = builder.Function("GetSensitivePersonalDataBySystemId"); + getSensitivePersonalDataBySystemId.Parameter("id"); + getSensitivePersonalDataBySystemId.ReturnsCollectionFromEntitySet("SensistivePersonalDataTypes"); + builder.StructuralTypes.First(t => t.ClrType == typeof(SensitivePersonalDataType)).AddProperty(typeof(SensitivePersonalDataType).GetProperty(nameof(SensitivePersonalDataType.Checked))); + getSensitivePersonalDataBySystemId.Namespace = "gdpr"; + + var getRegularPersonalDataBySystemId = builder.Function("GetRegularPersonalDataBySystemId"); + getRegularPersonalDataBySystemId.ReturnsCollectionFromEntitySet("RegularPersonalDataTypes") + .Parameter("id"); + builder.StructuralTypes.First(t => t.ClrType == typeof(RegularPersonalDataType)).AddProperty(typeof(RegularPersonalDataType).GetProperty(nameof(SensitivePersonalDataType.Checked))); + + var getRegularPersonalDataByUsageId = builder.Function("GetRegularPersonalDataByUsageId"); + getRegularPersonalDataByUsageId.ReturnsCollectionFromEntitySet("RegularPersonalDataTypes") + .Parameter("id"); + builder.StructuralTypes.First(t => t.ClrType == typeof(RegularPersonalDataType)).AddProperty(typeof(RegularPersonalDataType).GetProperty(nameof(SensitivePersonalDataType.Checked))); + + var getRegisterTypeByObjectId = builder.Function("GetRegisterTypesByObjectID"); + getRegisterTypeByObjectId.Parameter("id"); + getRegisterTypeByObjectId.ReturnsCollectionFromEntitySet("RegisterTypes"); + builder.StructuralTypes.First(t => t.ClrType == typeof(RegisterType)).AddProperty(typeof(RegisterType).GetProperty(nameof(SensitivePersonalDataType.Checked))); + + var localSensitiveDataType = BindEntitySet (builder); + localSensitiveDataType.HasRequiredBinding(u => u.Organization, entitySetOrganizations); + + var localSensistivePersonalDataTypes = BindEntitySet (builder); + localSensistivePersonalDataTypes.HasRequiredBinding(u => u.Organization, entitySetOrganizations); + + var localRegularPersonalDataTypes = BindEntitySet (builder); + localRegularPersonalDataTypes.HasRequiredBinding(u => u.Organization, entitySetOrganizations); + + var localRegisterTypes = BindEntitySet (builder); + localRegisterTypes.HasRequiredBinding(u => u.Organization, entitySetOrganizations); + + var localTerminationDeadlineType = BindEntitySet (builder); + localTerminationDeadlineType.HasRequiredBinding(u => u.Organization, entitySetOrganizations); + + var localTsaType = BindEntitySet (builder); + localTsaType.HasRequiredBinding(u => u.Organization, entitySetOrganizations); + + var localOrganizationUnitRole = BindEntitySet (builder); + localOrganizationUnitRole.HasRequiredBinding(u => u.Organization, entitySetOrganizations); + + BindEntitySet (builder); + + var config = BindEntitySet (builder); + config.HasRequiredBinding(u => u.Organization, entitySetOrganizations); + + + BindEntitySet (builder); + + BindEntitySet (builder); + + var getAdvicesByObjectId = builder.Function("GetAdvicesByObjectID"); + getAdvicesByObjectId.Parameter("id"); + getAdvicesByObjectId.Parameter("type"); + getAdvicesByObjectId.ReturnsCollectionFromEntitySet("Advice"); + + + BindEntitySet (builder); + + var accessType = BindEntitySet (builder); + accessType.HasRequiredBinding(a => a.ItSystem, entitySetItSystems); + + BindEntitySet (builder); var reports = builder.EntitySet("Reports"); - reports.HasRequiredBinding(u => u.Organization, "Organizations"); - reports.EntityType.HasKey(x => x.Id); + reports.HasRequiredBinding(u => u.Organization, entitySetOrganizations); var references = builder.EntitySet("ExternalReferences"); references.EntityType.HasKey(x => x.Id); - references.HasRequiredBinding(a => a.ItSystem, "ItSystems"); + references.HasRequiredBinding(a => a.ItSystem, entitySetItSystems); - var reportCategoryTypes = builder.EntitySet(nameof(ReportCategoryTypesController).Replace("Controller", string.Empty)); - reportCategoryTypes.EntityType.HasKey(x => x.Id); + BindEntitySet (builder); - var helpTexts = builder.EntitySet(nameof(HelpTextsController).Replace("Controller", string.Empty)); - helpTexts.EntityType.HasKey(x => x.Id); + BindEntitySet (builder); - var itProjectStatusUpdates = builder.EntitySet(nameof(ItProjectStatusUpdatesController).Replace("Controller", string.Empty)); - itProjectStatusUpdates.EntityType.HasKey(x => x.Id); - itProjectStatusUpdates.HasRequiredBinding(o => o.Organization, "Organizations"); + var itProjectStatusUpdates = BindEntitySet (builder); + itProjectStatusUpdates.HasRequiredBinding(o => o.Organization, entitySetOrganizations); return builder.GetEdmModel(); } + + private static EntitySetConfiguration BindEntitySet(ODataConventionModelBuilder builder) where TEntitySet : Entity + { + var entitySetConfiguration = BindTypeSet(builder); + entitySetConfiguration.EntityType.HasKey(x => x.Id); + return entitySetConfiguration; + } + + private static EntitySetConfiguration BindTypeSet(ODataConventionModelBuilder builder) where TEntitySet : class + { + return builder.EntitySet(typeof(TController).Name.Replace(ControllerSuffix, string.Empty)); + } } } diff --git a/Presentation.Web/Controllers/API/AdviceUserRelationController.cs b/Presentation.Web/Controllers/API/AdviceUserRelationController.cs index 1f77bdbf1a..758d393ac4 100644 --- a/Presentation.Web/Controllers/API/AdviceUserRelationController.cs +++ b/Presentation.Web/Controllers/API/AdviceUserRelationController.cs @@ -2,14 +2,14 @@ using Core.DomainServices; using Presentation.Web.Models; using System; -using System.Collections.Generic; using System.Linq; using System.Net.Http; -using System.Web; using System.Web.Mvc; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.API { + [PublicApi] public class AdviceUserRelationController : GenericApiController { IGenericRepository _repository; @@ -17,7 +17,11 @@ public AdviceUserRelationController(IGenericRepository repos { _repository = repository; } - + /// + /// Sletter adviser med det specificerede id fra en genereisk advis + /// + /// + /// [HttpDelete] public virtual HttpResponseMessage DeleteByAdviceId(int adviceId) { diff --git a/Presentation.Web/Controllers/API/AgreementElementController.cs b/Presentation.Web/Controllers/API/AgreementElementController.cs index d00581cb75..2bc08df030 100644 --- a/Presentation.Web/Controllers/API/AgreementElementController.cs +++ b/Presentation.Web/Controllers/API/AgreementElementController.cs @@ -1,11 +1,17 @@ using Core.DomainModel.ItContract; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class AgreementElementController : GenericOptionApiController { + /// + /// Nedarver fra base controlleren, udstiller Aftaleelementer of it kontrakter + /// + /// public AgreementElementController(IGenericRepository repository) : base(repository) { diff --git a/Presentation.Web/Controllers/API/ArchiveLocationController.cs b/Presentation.Web/Controllers/API/ArchiveLocationController.cs index 53490bc141..76798838d0 100644 --- a/Presentation.Web/Controllers/API/ArchiveLocationController.cs +++ b/Presentation.Web/Controllers/API/ArchiveLocationController.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.API { @@ -11,8 +8,13 @@ namespace Presentation.Web.Controllers.API using Presentation.Web.Models; + [PublicApi] public class ArchiveLocationController : GenericOptionApiController { + /// + /// Arkiveringssted for it system anvendelse fanen arkivering + /// + /// public ArchiveLocationController(IGenericRepository repository) : base(repository) { diff --git a/Presentation.Web/Controllers/API/ArchivePeriodController.cs b/Presentation.Web/Controllers/API/ArchivePeriodController.cs index 1b0f68b3ed..a661107f34 100644 --- a/Presentation.Web/Controllers/API/ArchivePeriodController.cs +++ b/Presentation.Web/Controllers/API/ArchivePeriodController.cs @@ -1,25 +1,35 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; +using System.Net; using System.Net.Http; -using System.Web; using System.Web.Http; -using System.Web.Mvc; using Core.DomainModel.ItSystem; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.API { + [PublicApi] public class ArchivePeriodController : GenericApiController { // GET: ArchivePeriod - + /// + /// Arkiveringsperiode for it system anvedelse på fanen arkivering + /// + /// public ArchivePeriodController(IGenericRepository repository) : base(repository) { } - + /// + /// Henter en enkelt enhed fra it system anvendelsens arkiv periode + /// + /// + /// + /// + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] + [SwaggerResponse(HttpStatusCode.NotFound)] public HttpResponseMessage GetSingle(int id, [FromUri] bool system) { var item = Repository.Get(x => x.ItSystemUsageId == id); diff --git a/Presentation.Web/Controllers/API/ArchiveTestLocationController.cs b/Presentation.Web/Controllers/API/ArchiveTestLocationController.cs index 659165b2cb..b84c626cf8 100644 --- a/Presentation.Web/Controllers/API/ArchiveTestLocationController.cs +++ b/Presentation.Web/Controllers/API/ArchiveTestLocationController.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.API { @@ -11,8 +8,13 @@ namespace Presentation.Web.Controllers.API using Presentation.Web.Models; + [PublicApi] public class ArchiveTestLocationController : GenericOptionApiController { + /// + /// Arkiveringsteststed fra it system anvendelsen, arkiverings fanen + /// + /// public ArchiveTestLocationController(IGenericRepository repository) : base(repository) { diff --git a/Presentation.Web/Controllers/API/ArchiveTypeController.cs b/Presentation.Web/Controllers/API/ArchiveTypeController.cs index 6832accd0a..80604ec117 100644 --- a/Presentation.Web/Controllers/API/ArchiveTypeController.cs +++ b/Presentation.Web/Controllers/API/ArchiveTypeController.cs @@ -1,10 +1,12 @@ using Core.DomainModel.ItSystem; using Core.DomainModel.ItSystemUsage; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class ArchiveTypeController : GenericOptionApiController { public ArchiveTypeController(IGenericRepository repository) diff --git a/Presentation.Web/Controllers/API/AssignmentController.cs b/Presentation.Web/Controllers/API/AssignmentController.cs index a7c6b46670..f2ebaaa944 100644 --- a/Presentation.Web/Controllers/API/AssignmentController.cs +++ b/Presentation.Web/Controllers/API/AssignmentController.cs @@ -1,9 +1,11 @@ using Core.DomainModel.ItProject; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class AssignmentController : GenericContextAwareApiController { public AssignmentController(IGenericRepository repository) diff --git a/Presentation.Web/Controllers/API/AuthorizeController.cs b/Presentation.Web/Controllers/API/AuthorizeController.cs index 86c8103f1b..37b7abf523 100644 --- a/Presentation.Web/Controllers/API/AuthorizeController.cs +++ b/Presentation.Web/Controllers/API/AuthorizeController.cs @@ -2,7 +2,6 @@ using System.Linq; using System.Net.Http; using System.Security.Claims; -using System.Security.Principal; using System.Web.Http; using System.Web.Security; using Core.DomainModel; @@ -11,9 +10,13 @@ using Presentation.Web.Infrastructure; using Presentation.Web.Models; using System.Collections.Generic; +using System.Net; +using Presentation.Web.Infrastructure.Attributes; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.API { + [PublicApi] public class AuthorizeController : BaseApiController { private readonly IUserRepository _userRepository; @@ -26,6 +29,7 @@ public AuthorizeController(IUserRepository userRepository, IUserService userServ _organizationService = organizationService; } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO))] public HttpResponseMessage GetLogin() { var user = KitosUser; @@ -43,6 +47,7 @@ public HttpResponseMessage GetLogin() } [Route("api/authorize/GetOrganizations")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetOrganizations() { var user = KitosUser; @@ -52,10 +57,11 @@ public HttpResponseMessage GetOrganizations() } [Route("api/authorize/GetOrganization({orgId})")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO))] public HttpResponseMessage GetOrganization(int orgId) { var user = KitosUser; - var org = _organizationService.GetOrganizations(user).Single(o=>o.Id == orgId); + var org = _organizationService.GetOrganizations(user).Single(o => o.Id == orgId); var defaultUnit = _organizationService.GetDefaultUnit(org, user); var dto = new OrganizationAndDefaultUnitDTO() { @@ -77,7 +83,7 @@ private User LoginWithToken(string token) if (principal.Claims.Any(c => c.Type == ClaimTypes.Email || c.Type == ClaimTypes.NameIdentifier)) { - + var emailClaim = principal.Claims.SingleOrDefault(c => c.Type == ClaimTypes.Email); var uuidClaim = principal.Claims.SingleOrDefault(c => c.Type == ClaimTypes.NameIdentifier); if (uuidClaim != null && !String.IsNullOrEmpty(uuidClaim.Value)) @@ -97,15 +103,76 @@ private User LoginWithToken(string token) } return user; } + //Post api/authorize/gettoken + [HttpPost] + [AllowAnonymous] + [Route("api/authorize/GetToken")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO))] + [SwaggerResponse(HttpStatusCode.BadRequest)] + [SwaggerResponse(HttpStatusCode.Forbidden)] + public HttpResponseMessage GetToken(LoginDTO loginDto) + { + if (loginDto == null) + { + return BadRequest(); + } + + if (string.IsNullOrWhiteSpace(loginDto.Email) || string.IsNullOrWhiteSpace(loginDto.Password)) + { + return BadRequest(); + } + try + { + if (!Membership.ValidateUser(loginDto.Email, loginDto.Password)) + { + Logger.Info("Attempt to login with bad credentials"); + return Unauthorized("Bad credentials"); + } + + var user = _userRepository.GetByEmail(loginDto.Email); + if (user == null) + { + Logger.Error("User found during membership validation but could not be found by email: {email}", loginDto.Email); + return BadRequest(); + } + + if (!user.HasApiAccess.GetValueOrDefault()) + { + Logger.Warn("User with Id {id} tried to use get a token for the API but was forbidden", user.Id); + return Forbidden(); + } + + var token = new TokenValidator().CreateToken(user); + + var response = new GetTokenResponseDTO + { + Token = token.Value, + Email = loginDto.Email, + LoginSuccessful = true, + Expires = token.Expiration + }; + + Logger.Info($"Created token for user with Id {user.Id}"); + + return Ok(response); + } + catch (Exception e) + { + Logger.Error(e, "Failed to create token"); + return LogError(e); + } + } // POST api/Authorize [AllowAnonymous] public HttpResponseMessage PostLogin(LoginDTO loginDto) { - var loginInfo = new { Token="", Email = "", Password = "", LoginSuccessful = false }; + if (loginDto == null) + { + return BadRequest(); + } - if (loginDto != null) - loginInfo = new { Token = loginDto.Token, Email = loginDto.Email, Password = "********", LoginSuccessful = false }; + var loginInfo = new { Email = loginDto.Email, LoginSuccessful = false }; try { @@ -115,34 +182,33 @@ public HttpResponseMessage PostLogin(LoginDTO loginDto) user = LoginWithToken(loginDto.Token); if (user == null) { - throw new ArgumentException(); + Logger.Info($"Uservalidation: Unsuccessful login with token. {loginInfo}"); + return Unauthorized("Invalid token"); } - } else { if (!Membership.ValidateUser(loginDto.Email, loginDto.Password)) { - throw new ArgumentException(); + Logger.Info($"Uservalidation: Unsuccessful login with credentials. {loginInfo}"); + return Unauthorized("Bad credentials"); } user = _userRepository.GetByEmail(loginDto.Email); + if (user == null) + { + Logger.Error($"User found during membership validation but could not be found by email: {loginDto.Email}"); + return BadRequest(); + } } - FormsAuthentication.SetAuthCookie(user.Id.ToString(), loginDto.RememberMe); var response = Map(user); - loginInfo = new { loginDto.Token, loginDto.Email, Password = "********", LoginSuccessful = true }; + loginInfo = new { loginDto.Email, LoginSuccessful = true }; Logger.Info($"Uservalidation: Successful {loginInfo}"); return Created(response); } - catch (ArgumentException) - { - Logger.Info($"Uservalidation: Unsuccessful. {loginInfo}"); - - return Unauthorized("Bad credentials"); - } catch (Exception e) { Logger.Info($"Uservalidation: Error. {loginInfo}"); @@ -181,30 +247,5 @@ public HttpResponseMessage PostResetpassword(bool? resetPassword, ResetPasswordD return LogError(e); } } - - // helper function - private LoginResponseDTO CreateLoginResponse(User user, IEnumerable organizations) - { - var userDto = AutoMapper.Mapper.Map(user); - - // getting the default org units (one or null for each organization) - var defaultUnits = organizations.Select(org => _organizationService.GetDefaultUnit(org, user)); - - // creating DTOs - var orgsDto = organizations.Zip(defaultUnits, (org, defaultUnit) => new OrganizationAndDefaultUnitDTO() - { - Organization = AutoMapper.Mapper.Map(org), - DefaultOrgUnit = AutoMapper.Mapper.Map(defaultUnit) - }); - - - var response = new LoginResponseDTO() - { - User = userDto, - Organizations = orgsDto - }; - - return response; - } } } diff --git a/Presentation.Web/Controllers/API/BaseApiController.cs b/Presentation.Web/Controllers/API/BaseApiController.cs index 8bd903df6c..4480740ad9 100644 --- a/Presentation.Web/Controllers/API/BaseApiController.cs +++ b/Presentation.Web/Controllers/API/BaseApiController.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; @@ -7,12 +6,15 @@ using System.Security; using System.Web.Http; using Core.ApplicationServices; +using Core.ApplicationServices.Authorization; using Core.DomainModel; using Core.DomainServices; +using Core.DomainServices.Authorization; using Ninject; using Ninject.Extensions.Logging; using Presentation.Web.Models; -using System.Runtime.Caching; +using Presentation.Web.Helpers; +using Presentation.Web.Infrastructure.Authorization.Controller; namespace Presentation.Web.Controllers.API { @@ -31,6 +33,21 @@ public abstract class BaseApiController : ApiController [Inject] public ILogger Logger { get; set; } + //Lazy to make sure auth service is available when resolved + private readonly Lazy _authorizationStrategy; + + protected IControllerAuthorizationStrategy AuthorizationStrategy => _authorizationStrategy.Value; + + protected BaseApiController(IAuthorizationContext authorizationContext = null) + { + _authorizationStrategy = new Lazy(() => + + authorizationContext == null + ? (IControllerAuthorizationStrategy)new LegacyAuthorizationStrategy(AuthenticationService, () => UserId) + : new ContextBasedAuthorizationStrategy(authorizationContext) + ); + } + protected HttpResponseMessage LogError(Exception exp, [CallerMemberName] string memberName = "") { Logger?.Error(exp, memberName); @@ -80,14 +97,22 @@ protected HttpResponseMessage Created(T response, Uri location = null) protected virtual HttpResponseMessage Error(T response) { - if (response is SecurityException) return Unauthorized(); + if (response is SecurityException) + { + return Unauthorized(); + } return CreateResponse(HttpStatusCode.InternalServerError, response); } + protected virtual HttpResponseMessage BadRequest() + { + return CreateResponse(HttpStatusCode.BadRequest); + } + protected virtual HttpResponseMessage Unauthorized() { - return CreateResponse(HttpStatusCode.Unauthorized); + return CreateResponse(HttpStatusCode.Unauthorized, Constants.StatusCodeMessages.UnauthorizedErrorMessage); } protected virtual HttpResponseMessage Unauthorized(T response) @@ -117,9 +142,15 @@ protected HttpResponseMessage NotAllowed() protected HttpResponseMessage Forbidden() { - return CreateResponse(HttpStatusCode.Forbidden); + return CreateResponse(HttpStatusCode.Forbidden, Constants.StatusCodeMessages.ForbiddenErrorMessage); } + protected HttpResponseMessage Forbidden(string msg) + { + return CreateResponse(HttpStatusCode.Forbidden, msg); + } + + protected bool IsGlobalAdmin() { try @@ -141,8 +172,7 @@ protected User KitosUser { try { - var id = Convert.ToInt32(User.Identity.Name); - var user = UserRepository.GetByKey(id); + var user = UserRepository.GetByKey(UserId); if (user == null) throw new SecurityException(); @@ -157,6 +187,8 @@ protected User KitosUser } } + protected int UserId => Convert.ToInt32(User.Identity.Name); + protected bool IsAuthenticated => User.Identity.IsAuthenticated; protected virtual TDest Map(TSource item) @@ -177,7 +209,60 @@ protected virtual IQueryable Page(IQueryable query, PagingModel pagi Newtonsoft.Json.JsonConvert.SerializeObject( paginationHeader)); - return query.OrderByField(paging.OrderBy, paging.Descending).Skip(paging.Skip).Take(paging.Take); + //Make sure query is ordered + query = query.OrderByField(paging.OrderBy, paging.Descending); + + //Apply post-processing + query = paging.ApplyPostProcessing(query); + + //Load the page + return query + .Skip(paging.Skip) + .Take(paging.Take); + } + + #region access control + + protected CrossOrganizationDataReadAccessLevel GetCrossOrganizationReadAccessLevel() + { + return AuthorizationStrategy.GetCrossOrganizationReadAccess(); + } + + protected OrganizationDataReadAccessLevel GetOrganizationReadAccessLevel(int organizationId) + { + return AuthorizationStrategy.GetOrganizationReadAccessLevel(organizationId); + } + + protected bool AllowRead(IEntity entity) + { + return AuthorizationStrategy.AllowRead(entity); + } + + protected bool AllowModify(IEntity entity) + { + return AuthorizationStrategy.AllowModify(entity); + } + + protected bool AllowCreate(IEntity entity) + { + return AuthorizationStrategy.AllowCreate(entity); + } + + protected bool AllowCreate() + { + return AuthorizationStrategy.AllowCreate(); + } + + protected bool AllowDelete(IEntity entity) + { + return AuthorizationStrategy.AllowDelete(entity); + } + + protected bool AllowEntityVisibilityControl(IEntity entity) + { + return AuthorizationStrategy.AllowEntityVisibilityControl(entity); } + + #endregion } } diff --git a/Presentation.Web/Controllers/API/BusinessTypeController.cs b/Presentation.Web/Controllers/API/BusinessTypeController.cs index 7ecec21c51..6ae9a742e9 100644 --- a/Presentation.Web/Controllers/API/BusinessTypeController.cs +++ b/Presentation.Web/Controllers/API/BusinessTypeController.cs @@ -1,9 +1,11 @@ using Core.DomainModel.ItSystem; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class BusinessTypeController : GenericOptionApiController { public BusinessTypeController(IGenericRepository repository) : base(repository) diff --git a/Presentation.Web/Controllers/API/CommunicationController.cs b/Presentation.Web/Controllers/API/CommunicationController.cs index 906043f53b..af86651ba8 100644 --- a/Presentation.Web/Controllers/API/CommunicationController.cs +++ b/Presentation.Web/Controllers/API/CommunicationController.cs @@ -1,11 +1,16 @@ -using System.Net.Http; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; using System.Web.Http; using Core.DomainModel.ItProject; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.API { + [PublicApi] public class CommunicationController : GenericContextAwareApiController { public CommunicationController(IGenericRepository repository) @@ -13,6 +18,8 @@ public CommunicationController(IGenericRepository repository) { } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] + [SwaggerResponse(HttpStatusCode.NotFound)] public HttpResponseMessage GetSingle(int id, [FromUri] bool project) { var item = Repository.Get(x => x.ItProjectId == id); diff --git a/Presentation.Web/Controllers/API/ConfigController.cs b/Presentation.Web/Controllers/API/ConfigController.cs index 0f7a05f984..98c73d25c4 100644 --- a/Presentation.Web/Controllers/API/ConfigController.cs +++ b/Presentation.Web/Controllers/API/ConfigController.cs @@ -1,9 +1,11 @@ using Core.DomainModel; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class ConfigController : GenericContextAwareApiController { public ConfigController(IGenericRepository repository) diff --git a/Presentation.Web/Controllers/API/ContactpersonController.cs b/Presentation.Web/Controllers/API/ContactpersonController.cs index 4e7c71e8e8..34b98d33cb 100644 --- a/Presentation.Web/Controllers/API/ContactpersonController.cs +++ b/Presentation.Web/Controllers/API/ContactpersonController.cs @@ -1,33 +1,33 @@ -using Core.ApplicationServices; -using Core.DomainModel; +using Core.DomainModel; using Core.DomainServices; using Presentation.Web.Models; using System; -using System.Collections.Generic; using System.Linq; -using System.Web; -using System.Web.Mvc; +using System.Net; using System.Net.Http; using Core.DomainModel.Organization; +using Presentation.Web.Infrastructure.Attributes; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.API { + [PublicApi] public class ContactpersonController : GenericApiController { - private readonly IAuthenticationService _authService; private readonly IGenericRepository _repository; private readonly IGenericRepository _orgRepository; - public ContactpersonController(IGenericRepository repository, IAuthenticationService authService, - IGenericRepository orgRepository) + public ContactpersonController(IGenericRepository repository, IGenericRepository orgRepository) : base(repository) { - _authService = authService; _repository = repository; _orgRepository = orgRepository; } // GET DataProtectionAdvisor by OrganizationId + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO))] + [SwaggerResponse(HttpStatusCode.Forbidden)] + [SwaggerResponse(HttpStatusCode.NotFound)] public override HttpResponseMessage GetSingle(int id) { try @@ -61,7 +61,7 @@ public override HttpResponseMessage GetSingle(int id) if (!AuthenticationService.HasReadAccess(KitosUser.Id, item)) { - return Unauthorized(); + return Forbidden(); } var dto = Map(item); diff --git a/Presentation.Web/Controllers/API/ContractTemplateController.cs b/Presentation.Web/Controllers/API/ContractTemplateController.cs index 86e64f1f15..9eb692735a 100644 --- a/Presentation.Web/Controllers/API/ContractTemplateController.cs +++ b/Presentation.Web/Controllers/API/ContractTemplateController.cs @@ -1,9 +1,11 @@ using Core.DomainModel.ItContract; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class ContractTemplateController : GenericOptionApiController { public ContractTemplateController(IGenericRepository repository) diff --git a/Presentation.Web/Controllers/API/ContractTypeController.cs b/Presentation.Web/Controllers/API/ContractTypeController.cs index 2f12eef341..773bd90416 100644 --- a/Presentation.Web/Controllers/API/ContractTypeController.cs +++ b/Presentation.Web/Controllers/API/ContractTypeController.cs @@ -1,9 +1,11 @@ using Core.DomainModel.ItContract; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class ContractTypeController : GenericOptionApiController { public ContractTypeController(IGenericRepository repository) diff --git a/Presentation.Web/Controllers/API/DataProtectionAdvisorController.cs b/Presentation.Web/Controllers/API/DataProtectionAdvisorController.cs index c455a73e57..0d5b483fc7 100644 --- a/Presentation.Web/Controllers/API/DataProtectionAdvisorController.cs +++ b/Presentation.Web/Controllers/API/DataProtectionAdvisorController.cs @@ -1,16 +1,16 @@ -using Core.DomainModel.Advice; -using Core.DomainModel.Organization; +using Core.DomainModel.Organization; using Core.DomainServices; using Presentation.Web.Models; using System; -using System.Collections.Generic; using System.Linq; +using System.Net; using System.Net.Http; -using System.Web; -using System.Web.Mvc; +using Presentation.Web.Infrastructure.Attributes; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.API { + [PublicApi] public class DataProtectionAdvisorController : GenericApiController { IGenericRepository _repository; @@ -22,13 +22,19 @@ public DataProtectionAdvisorController(IGenericRepository } // GET DataProtectionAdvisor by OrganizationId + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO))] + [SwaggerResponse(HttpStatusCode.Forbidden)] + [SwaggerResponse(HttpStatusCode.NotFound)] public override HttpResponseMessage GetSingle(int id) { try { var organization = _orgRepository.GetByKey(id); - if (organization == null) return NotFound(); + if (organization == null) + { + return NotFound(); + } var item = Repository.AsQueryable().FirstOrDefault(d => d.OrganizationId == organization.Id); @@ -49,7 +55,7 @@ public override HttpResponseMessage GetSingle(int id) if (!AuthenticationService.HasReadAccess(KitosUser.Id, item)) { - return Unauthorized(); + return Forbidden(); } var dto = Map(item); diff --git a/Presentation.Web/Controllers/API/DataResponsibleController.cs b/Presentation.Web/Controllers/API/DataResponsibleController.cs index 18626f05d2..4ef396e897 100644 --- a/Presentation.Web/Controllers/API/DataResponsibleController.cs +++ b/Presentation.Web/Controllers/API/DataResponsibleController.cs @@ -1,16 +1,16 @@ -using Core.DomainModel.Advice; -using Core.DomainModel.Organization; +using Core.DomainModel.Organization; using Core.DomainServices; using Presentation.Web.Models; using System; -using System.Collections.Generic; using System.Linq; +using System.Net; using System.Net.Http; -using System.Web; -using System.Web.Mvc; +using Presentation.Web.Infrastructure.Attributes; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.API { + [PublicApi] public class DataResponsibleController : GenericApiController { IGenericRepository _repository; @@ -22,6 +22,9 @@ public DataResponsibleController(IGenericRepository repository, _orgRepository = orgRepository; } // GET DataProtectionAdvisor by OrganizationId + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO))] + [SwaggerResponse(HttpStatusCode.Forbidden)] + [SwaggerResponse(HttpStatusCode.NotFound)] public override HttpResponseMessage GetSingle(int id) { try @@ -55,7 +58,7 @@ public override HttpResponseMessage GetSingle(int id) if (!AuthenticationService.HasReadAccess(KitosUser.Id, item)) { - return Unauthorized(); + return Forbidden(); } var dto = Map(item); diff --git a/Presentation.Web/Controllers/API/DataRowController.cs b/Presentation.Web/Controllers/API/DataRowController.cs index 934a59f81f..e3214afc5d 100644 --- a/Presentation.Web/Controllers/API/DataRowController.cs +++ b/Presentation.Web/Controllers/API/DataRowController.cs @@ -1,11 +1,16 @@ using System; +using System.Collections.Generic; +using System.Net; using System.Net.Http; using Core.DomainModel.ItSystem; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.API { + [PublicApi] public class DataRowController : GenericContextAwareApiController { public DataRowController(IGenericRepository repository) @@ -13,6 +18,7 @@ public DataRowController(IGenericRepository repository) { } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public virtual HttpResponseMessage GetByInterface(int interfaceId) { try diff --git a/Presentation.Web/Controllers/API/DataRowUsageController.cs b/Presentation.Web/Controllers/API/DataRowUsageController.cs index b00a4e516c..83419833a9 100644 --- a/Presentation.Web/Controllers/API/DataRowUsageController.cs +++ b/Presentation.Web/Controllers/API/DataRowUsageController.cs @@ -3,9 +3,11 @@ using Core.DomainModel.ItSystemUsage; using Core.DomainServices; using Newtonsoft.Json.Linq; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.API { + [PublicApi] public class DataRowUsageController : BaseApiController { private readonly IGenericRepository _repository; diff --git a/Presentation.Web/Controllers/API/DataTypeController.cs b/Presentation.Web/Controllers/API/DataTypeController.cs index ba34b9980a..3825f8c078 100644 --- a/Presentation.Web/Controllers/API/DataTypeController.cs +++ b/Presentation.Web/Controllers/API/DataTypeController.cs @@ -1,9 +1,11 @@ using Core.DomainModel.ItSystem; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class DataTypeController : GenericOptionApiController { diff --git a/Presentation.Web/Controllers/API/DataworkerController.cs b/Presentation.Web/Controllers/API/DataworkerController.cs index de3ffc4b62..215ed73361 100644 --- a/Presentation.Web/Controllers/API/DataworkerController.cs +++ b/Presentation.Web/Controllers/API/DataworkerController.cs @@ -1,14 +1,11 @@ using Core.DomainModel.ItSystem; using Core.DomainServices; using Presentation.Web.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.Mvc; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.API { + [PublicApi] public class DataworkerController : GenericApiController { public DataworkerController(IGenericRepository repository) diff --git a/Presentation.Web/Controllers/API/EconomyStreamController.cs b/Presentation.Web/Controllers/API/EconomyStreamController.cs index 84d25326a9..5b221c9d9c 100644 --- a/Presentation.Web/Controllers/API/EconomyStreamController.cs +++ b/Presentation.Web/Controllers/API/EconomyStreamController.cs @@ -5,9 +5,14 @@ using System.Net.Http; using Core.DomainModel; using System; +using System.Collections.Generic; +using System.Net; +using Presentation.Web.Infrastructure.Attributes; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.API { + [PublicApi] public class EconomyStreamController : GenericContextAwareApiController { private readonly IGenericRepository _contracts; @@ -17,6 +22,8 @@ public EconomyStreamController(IGenericRepository repository, IGe this._contracts = contracts; } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] + [SwaggerResponse(HttpStatusCode.Forbidden)] public HttpResponseMessage GetExternEconomyStreamForContract(int externPaymentForContractWithId) { var result = Repository.AsQueryable().Where(e => e.ExternPaymentForId == externPaymentForContractWithId); @@ -29,8 +36,11 @@ public HttpResponseMessage GetExternEconomyStreamForContract(int externPaymentFo // all users may view economy streams marked Public or if they are part of the organization result = result.Where(x => x.AccessModifier == AccessModifier.Public || x.ExternPaymentFor.OrganizationId == currentOrgId); if (!result.Any()) - //at this point the economy streams are marked Local but the user is not part of the organization which means they are not authorized to view the data - return Unauthorized(); + { + //at this point the economy streams are marked Local but the user is not part of the organization which means they are not allowed to view the data + return Forbidden(); + + } } } else @@ -41,6 +51,8 @@ public HttpResponseMessage GetExternEconomyStreamForContract(int externPaymentFo return Ok(Map(result)); } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] + [SwaggerResponse(HttpStatusCode.Forbidden)] public HttpResponseMessage GetInternEconomyStreamForContract(int internPaymentForContractWithId) { var result = Repository.AsQueryable().Where(e => e.InternPaymentForId == internPaymentForContractWithId); @@ -54,7 +66,9 @@ public HttpResponseMessage GetInternEconomyStreamForContract(int internPaymentFo result = result.Where(x => x.AccessModifier == AccessModifier.Public || x.InternPaymentFor.OrganizationId == currentOrgId); if (!result.Any()) //at this point the economy streams are marked Local but the user is not part of the organization which means they are not authorized to view the data - return Unauthorized(); + { + return Forbidden(); + } } } else @@ -87,7 +101,7 @@ public HttpResponseMessage Post(int contractId, EconomyStreamDTO streamDTO) if (!AuthenticationService.HasWriteAccess(KitosUser.Id, stream)) { - return Unauthorized(); + return Forbidden(); } stream.ObjectOwner = KitosUser; diff --git a/Presentation.Web/Controllers/API/EconomyYearController.cs b/Presentation.Web/Controllers/API/EconomyYearController.cs index 02c51905b1..b4798bfa64 100644 --- a/Presentation.Web/Controllers/API/EconomyYearController.cs +++ b/Presentation.Web/Controllers/API/EconomyYearController.cs @@ -1,26 +1,15 @@ using Core.DomainModel.ItProject; using Core.DomainServices; -using Newtonsoft.Json.Linq; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class EconomyYearController : GenericContextAwareApiController { public EconomyYearController(IGenericRepository repository) : base(repository) { } - - protected override EconomyYear PatchQuery(EconomyYear item, JObject obj) - { - CheckHasWriteAccess(); - - return base.PatchQuery(item, obj); - } - - private void CheckHasWriteAccess() - { - //TODO - } } } diff --git a/Presentation.Web/Controllers/API/ExcelController.cs b/Presentation.Web/Controllers/API/ExcelController.cs index 666488a695..bd0e3ccacf 100644 --- a/Presentation.Web/Controllers/API/ExcelController.cs +++ b/Presentation.Web/Controllers/API/ExcelController.cs @@ -7,10 +7,12 @@ using System.Web; using System.Web.Http; using Core.ApplicationServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [InternalApi] public class ExcelController : BaseApiController { private readonly IExcelService _excelService; diff --git a/Presentation.Web/Controllers/API/ExhibitController.cs b/Presentation.Web/Controllers/API/ExhibitController.cs index ac808a5645..58457508df 100644 --- a/Presentation.Web/Controllers/API/ExhibitController.cs +++ b/Presentation.Web/Controllers/API/ExhibitController.cs @@ -1,15 +1,19 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Net.Http; using AutoMapper; using Core.DomainModel; using Core.DomainModel.ItSystem; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.API { + [PublicApi] public class ExhibitController : GenericContextAwareApiController { private readonly IGenericRepository _repository; @@ -20,6 +24,7 @@ public ExhibitController(IGenericRepository repository) _repository = repository; } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetInterfacesBySystem(int sysId, int orgId, bool? interfaces) { try @@ -35,6 +40,7 @@ public HttpResponseMessage GetInterfacesBySystem(int sysId, int orgId, bool? int } } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetBySystem(int sysId, int orgId, string q) { try diff --git a/Presentation.Web/Controllers/API/FrequencyController.cs b/Presentation.Web/Controllers/API/FrequencyController.cs index 0ff6aced5d..1e801b02c7 100644 --- a/Presentation.Web/Controllers/API/FrequencyController.cs +++ b/Presentation.Web/Controllers/API/FrequencyController.cs @@ -1,10 +1,12 @@ using Core.DomainModel.ItSystem; using Core.DomainModel.ItSystemUsage; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class FrequencyController : GenericOptionApiController { public FrequencyController(IGenericRepository repository) : base(repository) diff --git a/Presentation.Web/Controllers/API/GenericApiController.cs b/Presentation.Web/Controllers/API/GenericApiController.cs index 0860bb9630..5498651afc 100644 --- a/Presentation.Web/Controllers/API/GenericApiController.cs +++ b/Presentation.Web/Controllers/API/GenericApiController.cs @@ -4,11 +4,14 @@ using System.Net.Http; using System.Security; using System.Web.Http; +using Core.ApplicationServices.Authorization; using Core.DomainModel; using Newtonsoft.Json.Linq; using Presentation.Web.Models; using Presentation.Web.Models.Exceptions; using Core.DomainServices; +using Core.DomainServices.Authorization; +using Core.DomainServices.Queries; namespace Presentation.Web.Controllers.API { @@ -17,7 +20,10 @@ public abstract class GenericApiController : BaseApiController { protected readonly IGenericRepository Repository; - protected GenericApiController(IGenericRepository repository) + protected GenericApiController( + IGenericRepository repository, + IAuthorizationContext authorizationContext = null) + : base(authorizationContext) { Repository = repository; } @@ -27,34 +33,32 @@ protected virtual IQueryable GetAllQuery() return Repository.AsQueryable(); } + /// + /// Get all from base entity controller + /// + /// + /// public virtual HttpResponseMessage GetAll([FromUri] PagingModel paging) { try { - var hasOrg = typeof(IHasOrganization).IsAssignableFrom(typeof(TModel)); - var result = GetAllQuery().AsEnumerable(); + var organizationId = AuthenticationService.GetCurrentOrganizationId(UserId); - if (AuthenticationService.HasReadAccessOutsideContext(KitosUser.Id) || hasOrg == false) - { - if (typeof(IHasAccessModifier).IsAssignableFrom(typeof(TModel)) && !AuthenticationService.IsGlobalAdmin(KitosUser.Id)) - { - if (hasOrg) - { - result = result.Where(x => ((IHasAccessModifier)x).AccessModifier == AccessModifier.Public || ((IHasOrganization)x).OrganizationId == KitosUser.DefaultOrganizationId); - } - else - { - result = result.Where(x => ((IHasAccessModifier)x).AccessModifier == AccessModifier.Public); - } - } - } - else + var crossOrganizationReadAccess = GetCrossOrganizationReadAccessLevel(); + + var refinement = new QueryAllByRestrictionCapabilities(crossOrganizationReadAccess, organizationId); + + var result = refinement.Apply(Repository.AsQueryable()); + + if (refinement.RequiresPostFiltering()) { - result = result.Where(x => ((IHasOrganization)x).OrganizationId == KitosUser.DefaultOrganizationId); + paging = paging.WithPostProcessingFilter(AllowRead); } - var query = Page(result.AsQueryable(), paging); + var query = Page(result, paging); + var dtos = Map(query); + return Ok(dtos); } catch (Exception e) @@ -64,18 +68,26 @@ public virtual HttpResponseMessage GetAll([FromUri] PagingModel paging) } // GET api/T + /// + /// Get single from base entity controller + /// + /// + /// Single object from database related to the controller public virtual HttpResponseMessage GetSingle(int id) { try { var item = Repository.GetByKey(id); - if(!AuthenticationService.HasReadAccess(KitosUser.Id, item)) + if (!AllowRead(item)) { - return Unauthorized(); + return Forbidden(); } - if (item == null) return NotFound(); + if (item == null) + { + return NotFound(); + } var dto = Map(item); return Ok(dto); @@ -99,7 +111,10 @@ public HttpResponseMessage GetHasWriteAccess(int id, int organizationId, bool? h { try { - return Ok(HasWriteAccess(id, organizationId)); + var entity = Repository.GetByKey(id); + var allowWriteAccess = AllowModify(entity); + + return Ok(allowWriteAccess); } catch (Exception e) { @@ -107,6 +122,43 @@ public HttpResponseMessage GetHasWriteAccess(int id, int organizationId, bool? h } } + /// + /// GET api/T/GetAccessRights + /// Checks what access rights the user has for the given entities + /// + public HttpResponseMessage GetAccessRights(bool? getEntitiesAccessRights) + { + if (GetOrganizationReadAccessLevel(AuthenticationService.GetCurrentOrganizationId(UserId)) == OrganizationDataReadAccessLevel.None) + { + return Forbidden(); + } + return Ok(new EntitiesAccessRightsDTO + { + CanCreate = AllowCreate(), + CanView = true + }); + } + + /// + /// GET api/T/id?GetAccessRightsForEntity + /// Checks what access rights the user has for the given entity + /// + /// The id of the object + public HttpResponseMessage GetAccessRightsForEntity(int id, bool? getEntityAccessRights) + { + var item = Repository.GetByKey(id); + if (item == null) + { + return NotFound(); + } + return Ok(new EntityAccessRightsDTO + { + CanDelete = AllowDelete(item), + CanEdit = AllowModify(item), + CanView = AllowRead(item) + }); + } + protected virtual TModel PostQuery(TModel item) { var insertedItem = Repository.Insert(item); @@ -115,16 +167,25 @@ protected virtual TModel PostQuery(TModel item) return insertedItem; } - // POST api/T + /// + /// Post from base entity controller + /// + /// + /// HTML code for success or failure public virtual HttpResponseMessage Post(TDto dto) { try { var item = Map(dto); - item.ObjectOwner = KitosUser; item.LastChangedByUser = KitosUser; + // Check CREATE access rights + if (!AllowCreate(item)) + { + return Forbidden(); + } + var savedItem = PostQuery(item); return Created(Map(savedItem), new Uri(Request.RequestUri + "/" + savedItem.Id)); @@ -141,8 +202,12 @@ public virtual HttpResponseMessage Post(TDto dto) { // check if inner message is a duplicate, if so return conflict if (e.InnerException?.InnerException != null) + { if (e.InnerException.InnerException.Message.Contains("Duplicate entry")) + { return Conflict(e.InnerException.InnerException.Message); + } + } return LogError(e); } @@ -157,6 +222,13 @@ protected virtual TModel PutQuery(TModel item) } // PUT api/T + /// + /// Put from base entity controller + /// + /// + /// + /// + /// public virtual HttpResponseMessage Put(int id, int organizationId, JObject obj) { return Patch(id, organizationId, obj); @@ -169,12 +241,22 @@ protected virtual void DeleteQuery(TModel entity) } // DELETE api/T + /// + /// Delete from base entity controller + /// + /// + /// + /// public virtual HttpResponseMessage Delete(int id, int organizationId) { try { var item = Repository.GetByKey(id); - if (!HasWriteAccess(item, organizationId)) return Unauthorized(); + + if (!AllowDelete(item)) + { + return Forbidden(); + } DeleteQuery(item); @@ -199,13 +281,17 @@ protected virtual TModel PatchQuery(TModel item, JObject obj) { var mapMember = nonNullMaps.SingleOrDefault(x => x.SourceMember.Name.Equals(valuePair.Key, StringComparison.InvariantCultureIgnoreCase)); if (mapMember == null) + { continue; // abort if no map found + } var destName = mapMember.DestinationProperty.Name; var jToken = valuePair.Value; if (destName == "LastChangedByUserId" || destName == "LastChanged") + { continue; // don't allow writing to these. TODO This should really be done using in/out DTOs + } var propRef = itemType.GetProperty(destName); var t = propRef.PropertyType; @@ -232,6 +318,7 @@ protected virtual TModel PatchQuery(TModel item, JObject obj) propRef.SetValue(item, null); } } + // BUG JSON.NET throws on Guid // Bugreport https://json.codeplex.com/workitem/25599 else if (t.IsEquivalentTo(typeof(Guid))) @@ -270,13 +357,27 @@ protected virtual TModel PatchQuery(TModel item, JObject obj) } // PATCH api/T + /// + /// Patch from base entity controller + /// + /// + /// + /// + /// public virtual HttpResponseMessage Patch(int id, int organizationId, JObject obj) { try { var item = Repository.GetByKey(id); - if (item == null) return NotFound(); - if (!HasWriteAccess(item, organizationId)) return Unauthorized(); + if (item == null) + { + return NotFound(); + } + + if (!AllowModify(item)) + { + return Forbidden(); + } var result = PatchQuery(item, obj); return Ok(Map(result)); @@ -285,9 +386,15 @@ public virtual HttpResponseMessage Patch(int id, int organizationId, JObject obj { // check if inner message is a duplicate, if so return conflict if (e.InnerException != null) + { if (e.InnerException.InnerException != null) + { if (e.InnerException.InnerException.Message.Contains("Duplicate entry")) + { return Conflict(e.InnerException.InnerException.Message); + } + } + } return LogError(e); } @@ -308,36 +415,12 @@ protected override void Dispose(bool disposing) /// The object /// The user /// - /// True iff user has write access to obj + /// True if user has write access to obj protected virtual bool HasWriteAccess(TModel obj, User user, int organizationId) { return AuthenticationService.HasWriteAccess(user.Id, obj); } - /// - /// Checks if the current authenticated user has write access to a given object. - /// - /// The id of object - /// - /// True iff user has write access to the object with objId - protected bool HasWriteAccess(int objId, int organizationId) - { - return HasWriteAccess(objId, KitosUser, organizationId); - } - - /// - /// Checks if a given user has write access to a given object. - /// - /// The id of object - /// The user - /// - /// True iff user has write access to the object with objId - protected bool HasWriteAccess(int objId, User user, int organizationId) - { - var obj = Repository.GetByKey(objId); - return HasWriteAccess(obj, user, organizationId); - } - /// /// Checks if the current authenticated user has write access to a given object. /// @@ -378,6 +461,5 @@ protected virtual IEnumerable Map(IEnumerable inputDtos) } #endregion - } } diff --git a/Presentation.Web/Controllers/API/GenericContextAwareApiController.cs b/Presentation.Web/Controllers/API/GenericContextAwareApiController.cs index 906cd422e6..3a13c2198d 100644 --- a/Presentation.Web/Controllers/API/GenericContextAwareApiController.cs +++ b/Presentation.Web/Controllers/API/GenericContextAwareApiController.cs @@ -1,6 +1,5 @@ -using System.Linq; +using Core.ApplicationServices.Authorization; using Core.DomainModel; -using Core.DomainModel.Organization; using Core.DomainServices; namespace Presentation.Web.Controllers.API @@ -8,14 +7,9 @@ namespace Presentation.Web.Controllers.API public class GenericContextAwareApiController : GenericApiController where TModel : Entity, IContextAware { - public GenericContextAwareApiController(IGenericRepository repository) - : base(repository) + public GenericContextAwareApiController(IGenericRepository repository, IAuthorizationContext authorizationContext = null) + : base(repository, authorizationContext) { } - - protected override bool HasWriteAccess(TModel obj, User user, int organizationId) - { - return base.HasWriteAccess(obj, user, organizationId); - } } } diff --git a/Presentation.Web/Controllers/API/GenericHierarchyApiController.cs b/Presentation.Web/Controllers/API/GenericHierarchyApiController.cs index 3bdc9da122..af36749bfb 100644 --- a/Presentation.Web/Controllers/API/GenericHierarchyApiController.cs +++ b/Presentation.Web/Controllers/API/GenericHierarchyApiController.cs @@ -1,6 +1,7 @@ using System; using System.Net.Http; using System.Web.Http; +using Core.ApplicationServices.Authorization; using Core.DomainModel; using Core.DomainServices; using Newtonsoft.Json.Linq; @@ -11,8 +12,8 @@ namespace Presentation.Web.Controllers.API public abstract class GenericHierarchyApiController : GenericContextAwareApiController where TModel : Entity, IHierarchy, IContextAware { - protected GenericHierarchyApiController(IGenericRepository repository) - : base(repository) + protected GenericHierarchyApiController(IGenericRepository repository, IAuthorizationContext authorizationContext = null) + : base(repository, authorizationContext) { } diff --git a/Presentation.Web/Controllers/API/GenericOptionApiController.cs b/Presentation.Web/Controllers/API/GenericOptionApiController.cs index 903a67d878..93f312d787 100644 --- a/Presentation.Web/Controllers/API/GenericOptionApiController.cs +++ b/Presentation.Web/Controllers/API/GenericOptionApiController.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Net.Http; using System.Security; +using Core.ApplicationServices.Authorization; using Core.DomainModel; using Core.DomainServices; @@ -10,8 +11,8 @@ namespace Presentation.Web.Controllers.API public abstract class GenericOptionApiController : GenericApiController where TModel : OptionEntity { - protected GenericOptionApiController(IGenericRepository repository) - : base(repository) + protected GenericOptionApiController(IGenericRepository repository, IAuthorizationContext authorizationContext = null) + : base(repository, authorizationContext) { } @@ -50,7 +51,7 @@ public HttpResponseMessage GetAllNonSuggestions(bool? nonsuggestions) protected override TModel PutQuery(TModel item) { - if (!item.IsSuggestion && !IsGlobalAdmin()) + if (!item.IsSuggestion && !AllowModify(item)) throw new SecurityException(); return base.PutQuery(item); diff --git a/Presentation.Web/Controllers/API/GenericRightsController.cs b/Presentation.Web/Controllers/API/GenericRightsController.cs index 0168c50213..ee455b0a83 100644 --- a/Presentation.Web/Controllers/API/GenericRightsController.cs +++ b/Presentation.Web/Controllers/API/GenericRightsController.cs @@ -3,8 +3,8 @@ using System.Linq; using System.Net.Http; using System.Web.Http; +using Core.ApplicationServices.Authorization; using Core.DomainModel; -using Core.DomainModel.Organization; using Core.DomainServices; using Presentation.Web.Models; @@ -18,7 +18,11 @@ public abstract class GenericRightsController : BaseApiC protected readonly IGenericRepository RightRepository; private readonly IGenericRepository _objectRepository; - protected GenericRightsController(IGenericRepository rightRepository, IGenericRepository objectRepository) + protected GenericRightsController( + IGenericRepository rightRepository, + IGenericRepository objectRepository, + IAuthorizationContext authorizationContext = null) + : base(authorizationContext) { RightRepository = rightRepository; _objectRepository = objectRepository; @@ -44,6 +48,7 @@ public virtual HttpResponseMessage GetRights(int id) try { var theRights = GetRightsQuery(id); + theRights = theRights.Where(AllowRead); var dtos = Map, IEnumerable>(theRights); return Ok(dtos); @@ -65,8 +70,10 @@ public HttpResponseMessage PostRight(int id, int organizationId, RightInputDTO d { try { - if (!HasWriteAccess(id, KitosUser, organizationId)) - return Unauthorized(); + if (!HasWriteAccess(id)) + { + return Forbidden(); + } var right = AutoMapper.Mapper.Map(dto); right.ObjectId = id; @@ -102,8 +109,11 @@ public HttpResponseMessage Delete(int id, [FromUri] int rId, [FromUri] int uId, { try { - if (!HasWriteAccess(id, KitosUser, organizationId)) - return Unauthorized(); + if (!HasWriteAccess(id)) + { + return Forbidden(); + } + var right = RightRepository.Get(r => r.ObjectId == id && r.RoleId == rId && r.UserId == uId).FirstOrDefault(); @@ -120,23 +130,11 @@ public HttpResponseMessage Delete(int id, [FromUri] int rId, [FromUri] int uId, } } - private bool HasWriteAccess(int objectId, User user, int organizationId) + private bool HasWriteAccess(int objectId) { - - if (user.IsGlobalAdmin) - return true; - var obj = _objectRepository.GetByKey(objectId); - return AuthenticationService.HasWriteAccess(user.Id, obj); - - - //// local admin have write access if the obj is in context - //if (obj.IsInContext(organizationId) && - // user.OrganizationRights.Any(x => x.OrganizationId == organizationId && x.Role == OrganizationRole.LocalAdmin)) - // return true; - - //return obj.HasUserWriteAccess(user); + return AllowModify(obj); } } } diff --git a/Presentation.Web/Controllers/API/GlobalAdminController.cs b/Presentation.Web/Controllers/API/GlobalAdminController.cs index 593b89652e..976501688f 100644 --- a/Presentation.Web/Controllers/API/GlobalAdminController.cs +++ b/Presentation.Web/Controllers/API/GlobalAdminController.cs @@ -2,18 +2,22 @@ using System.Collections.Generic; using System.Net.Http; using System.Web.Http; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { - //TODO refactor this into OrganizationRightsController + [InternalApi] public class GlobalAdminController : BaseApiController { public HttpResponseMessage Get() { try { - if (!IsGlobalAdmin()) return Unauthorized(); + if (!IsGlobalAdmin()) + { + return Forbidden(); + } var users = UserRepository.Get(u => u.IsGlobalAdmin); @@ -32,12 +36,18 @@ public HttpResponseMessage Post(CreateGlobalAdminDTO dto) { try { - if (!IsGlobalAdmin()) return Unauthorized(); + if (!IsGlobalAdmin()) + { + return Forbidden(); + } var user = UserRepository.GetByKey(dto.UserId); //if already global admin, return conflict - if (user.IsGlobalAdmin) return Conflict(user.Name + " is already global admin"); + if (user.IsGlobalAdmin) + { + return Conflict(user.Name + " is already global admin"); + } user.IsGlobalAdmin = true; user.LastChanged = DateTime.UtcNow; @@ -59,16 +69,19 @@ public HttpResponseMessage Delete([FromUri] int userId) { try { - if (!IsGlobalAdmin()) return Unauthorized(); + if (IsGlobalAdmin()) + { + var user = UserRepository.GetByKey(userId); - var user = UserRepository.GetByKey(userId); + user.IsGlobalAdmin = false; + UserRepository.Save(); - user.IsGlobalAdmin = false; - UserRepository.Save(); + var outDto = AutoMapper.Mapper.Map(user); - var outDto = AutoMapper.Mapper.Map(user); + return Ok(outDto); + } - return Ok(outDto); + return Forbidden(); } catch (Exception e) diff --git a/Presentation.Web/Controllers/API/GoalController.cs b/Presentation.Web/Controllers/API/GoalController.cs index 354c4ade70..5a74c1c756 100644 --- a/Presentation.Web/Controllers/API/GoalController.cs +++ b/Presentation.Web/Controllers/API/GoalController.cs @@ -1,9 +1,11 @@ using Core.DomainModel.ItProject; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class GoalController : GenericApiController { public GoalController(IGenericRepository repository) : base(repository) diff --git a/Presentation.Web/Controllers/API/GoalStatusController.cs b/Presentation.Web/Controllers/API/GoalStatusController.cs index e7d846f5ef..169db31b48 100644 --- a/Presentation.Web/Controllers/API/GoalStatusController.cs +++ b/Presentation.Web/Controllers/API/GoalStatusController.cs @@ -1,9 +1,11 @@ using Core.DomainModel.ItProject; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class GoalStatusController : GenericContextAwareApiController { public GoalStatusController(IGenericRepository repository) : base(repository) diff --git a/Presentation.Web/Controllers/API/GoalTypeController.cs b/Presentation.Web/Controllers/API/GoalTypeController.cs index e2618fe106..7f6012316f 100644 --- a/Presentation.Web/Controllers/API/GoalTypeController.cs +++ b/Presentation.Web/Controllers/API/GoalTypeController.cs @@ -1,9 +1,11 @@ using Core.DomainModel.ItProject; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class GoalTypeController : GenericOptionApiController { public GoalTypeController(IGenericRepository repository) : base(repository) diff --git a/Presentation.Web/Controllers/API/HandoverController.cs b/Presentation.Web/Controllers/API/HandoverController.cs index d9b664bf82..cf447c27f3 100644 --- a/Presentation.Web/Controllers/API/HandoverController.cs +++ b/Presentation.Web/Controllers/API/HandoverController.cs @@ -4,10 +4,12 @@ using Core.DomainModel; using Core.DomainModel.ItProject; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class HandoverController : GenericContextAwareApiController { private readonly IGenericRepository _userRepository; diff --git a/Presentation.Web/Controllers/API/HandoverTrialController.cs b/Presentation.Web/Controllers/API/HandoverTrialController.cs index b2a0001ca5..cfe9b4f59d 100644 --- a/Presentation.Web/Controllers/API/HandoverTrialController.cs +++ b/Presentation.Web/Controllers/API/HandoverTrialController.cs @@ -1,10 +1,15 @@ -using System.Net.Http; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; using Core.DomainModel.ItContract; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.API { + [PublicApi] public class HandoverTrialController : GenericContextAwareApiController { public HandoverTrialController(IGenericRepository repository) @@ -12,6 +17,7 @@ public HandoverTrialController(IGenericRepository repository) { } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetByContractid(int id, bool? byContract) { var query = Repository.Get(x => x.ItContractId == id); diff --git a/Presentation.Web/Controllers/API/HandoverTrialTypeController.cs b/Presentation.Web/Controllers/API/HandoverTrialTypeController.cs index dc66e684c2..0c99fea726 100644 --- a/Presentation.Web/Controllers/API/HandoverTrialTypeController.cs +++ b/Presentation.Web/Controllers/API/HandoverTrialTypeController.cs @@ -1,9 +1,11 @@ using Core.DomainModel.ItContract; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class HandoverTrialTypeController : GenericOptionApiController { public HandoverTrialTypeController(IGenericRepository repository) diff --git a/Presentation.Web/Controllers/API/HealthCheckController.cs b/Presentation.Web/Controllers/API/HealthCheckController.cs index 2e74192247..2be7d7b5cf 100644 --- a/Presentation.Web/Controllers/API/HealthCheckController.cs +++ b/Presentation.Web/Controllers/API/HealthCheckController.cs @@ -1,9 +1,11 @@ using System.Web.Http; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Properties; namespace Presentation.Web.Controllers.API { [AllowAnonymous] + [PublicApi] public class HealthCheckController : ApiController { private static readonly string DeploymentVersion = Settings.Default.DeploymentVersion; diff --git a/Presentation.Web/Controllers/API/InterfaceController.cs b/Presentation.Web/Controllers/API/InterfaceController.cs index f70358ce15..0b9b97b2f2 100644 --- a/Presentation.Web/Controllers/API/InterfaceController.cs +++ b/Presentation.Web/Controllers/API/InterfaceController.cs @@ -1,9 +1,11 @@ using Core.DomainModel.ItSystem; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class InterfaceController : GenericOptionApiController { public InterfaceController(IGenericRepository repository) : base(repository) diff --git a/Presentation.Web/Controllers/API/InterfaceTypeController.cs b/Presentation.Web/Controllers/API/InterfaceTypeController.cs index c79d291405..09a9fe2455 100644 --- a/Presentation.Web/Controllers/API/InterfaceTypeController.cs +++ b/Presentation.Web/Controllers/API/InterfaceTypeController.cs @@ -1,9 +1,11 @@ using Core.DomainModel.ItSystem; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class InterfaceTypeController : GenericOptionApiController { public InterfaceTypeController(IGenericRepository repository) diff --git a/Presentation.Web/Controllers/API/ItContractController.cs b/Presentation.Web/Controllers/API/ItContractController.cs index 98422e1adf..a36d2673c3 100644 --- a/Presentation.Web/Controllers/API/ItContractController.cs +++ b/Presentation.Web/Controllers/API/ItContractController.cs @@ -1,30 +1,28 @@ using System; using System.Collections.Generic; -using System.Dynamic; -using System.IO; using System.Linq; using System.Net; using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; using System.Web.Http; using Core.ApplicationServices; using Core.DomainModel.ItContract; using Core.DomainModel.ItSystem; using Core.DomainModel.ItSystemUsage; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.API { using Core.DomainModel; using Core.DomainModel.Organization; + [PublicApi] public class ItContractController : GenericHierarchyApiController { private readonly IGenericRepository _agreementElementRepository; - private readonly IGenericRepository _roleRepository; private readonly IGenericRepository _itContractItSystemUsageRepository; private readonly IGenericRepository _usageRepository; private readonly IItContractService _itContractService; @@ -32,24 +30,28 @@ public class ItContractController : GenericHierarchyApiController repository, IGenericRepository usageRepository, IGenericRepository agreementElementRepository, - IGenericRepository roleRepository, IGenericRepository itContractItSystemUsageRepository, IItContractService itContractService) : base(repository) { _usageRepository = usageRepository; _agreementElementRepository = agreementElementRepository; - _roleRepository = roleRepository; _itContractItSystemUsageRepository = itContractItSystemUsageRepository; _itContractService = itContractService; } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] + [SwaggerResponse(HttpStatusCode.Forbidden)] + [SwaggerResponse(HttpStatusCode.NotFound)] public virtual HttpResponseMessage Get(string q, int orgId, [FromUri] PagingModel paging) { paging.Where(x => x.Name.Contains(q) && x.OrganizationId == orgId); return base.GetAll(paging); } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO))] + [SwaggerResponse(HttpStatusCode.Forbidden)] + [SwaggerResponse(HttpStatusCode.NotFound)] public override HttpResponseMessage GetSingle(int id) { try @@ -58,10 +60,13 @@ public override HttpResponseMessage GetSingle(int id) { if (!AuthenticationService.HasReadAccess(KitosUser.Id, item)) { - return Unauthorized(); + return Forbidden(); } - if (item == null) return NotFound(); + if (item == null) + { + return NotFound(); + } var dto = Map(item); @@ -83,7 +88,10 @@ public virtual HttpResponseMessage PostAgreementElement(int id, int organization try { var contract = Repository.GetByKey(id); - if (!HasWriteAccess(contract, organizationId)) return Unauthorized(); + if (!HasWriteAccess(contract, organizationId)) + { + return Forbidden(); + } var elem = _agreementElementRepository.GetByKey(elemId); @@ -109,7 +117,10 @@ public virtual HttpResponseMessage DeleteAgreementElement(int id, int organizati try { var contract = Repository.GetByKey(id); - if (!HasWriteAccess(contract, organizationId)) return Unauthorized(); + if (!HasWriteAccess(contract, organizationId)) + { + return Forbidden(); + } var elem = _agreementElementRepository.GetByKey(elemId); @@ -129,6 +140,8 @@ public virtual HttpResponseMessage DeleteAgreementElement(int id, int organizati } } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] + [SwaggerResponse(HttpStatusCode.Forbidden)] public virtual HttpResponseMessage GetExhibitedInterfaces(int id, bool? exhibit) { try @@ -136,7 +149,7 @@ public virtual HttpResponseMessage GetExhibitedInterfaces(int id, bool? exhibit) var contract = Repository.GetByKey(id); if (!AuthenticationService.HasReadAccess(KitosUser.Id, contract)) { - return Unauthorized(); + return Forbidden(); } var exhibits = contract.AssociatedInterfaceExposures.Select(x => x.ItInterfaceExhibit); var dtos = Map, IEnumerable>(exhibits); @@ -160,11 +173,21 @@ public HttpResponseMessage PostSystemUsage(int id, int organizationId, int syste try { var contract = Repository.GetByKey(id); - if (contract == null) return NotFound(); - if (!HasWriteAccess(contract, organizationId)) return Unauthorized(); + if (contract == null) + { + return NotFound(); + } + + if (!HasWriteAccess(contract, organizationId)) + { + return Forbidden(); + } var usage = _usageRepository.GetByKey(systemUsageId); - if (usage == null) return NotFound(); + if (usage == null) + { + return NotFound(); + } if (_itContractItSystemUsageRepository.GetByKey(new object[] { id, systemUsageId }) != null) return Conflict("The IT system usage is already associated with the contract"); @@ -195,7 +218,10 @@ public HttpResponseMessage DeleteSystemUsage(int id, int organizationId, int sys try { var contract = Repository.GetByKey(id); - if (!HasWriteAccess(contract, organizationId)) return Unauthorized(); + if (!HasWriteAccess(contract, organizationId)) + { + return Forbidden(); + } var contractItSystemUsage = _itContractItSystemUsageRepository.GetByKey(new object[] { id, systemUsageId }); if (contractItSystemUsage == null) @@ -215,6 +241,9 @@ public HttpResponseMessage DeleteSystemUsage(int id, int organizationId, int sys } } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] + [SwaggerResponse(HttpStatusCode.Forbidden)] + [SwaggerResponse(HttpStatusCode.NotFound)] public HttpResponseMessage GetHierarchy(int id, [FromUri] bool? hierarchy) { try @@ -226,7 +255,7 @@ public HttpResponseMessage GetHierarchy(int id, [FromUri] bool? hierarchy) if (!AuthenticationService.HasReadAccess(KitosUser.Id, itContract)) { - return Unauthorized(); + return Forbidden(); } // this trick will put the first object in the result as well as the children var children = new[] { itContract }.SelectNestedChildren(x => x.Children); @@ -242,11 +271,13 @@ public HttpResponseMessage GetHierarchy(int id, [FromUri] bool? hierarchy) } } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] + [SwaggerResponse(HttpStatusCode.Forbidden)] public HttpResponseMessage GetOverview(bool? overview, int organizationId, [FromUri] PagingModel pagingModel, [FromUri] string q) { if (KitosUser.DefaultOrganizationId != organizationId) { - return Unauthorized(); + return Forbidden(); } try @@ -271,74 +302,13 @@ public HttpResponseMessage GetOverview(bool? overview, int organizationId, [From } } - public HttpResponseMessage GetExcel(bool? csv, int organizationId) - { - try - { - //Get contracts within organization - var contracts = Repository.Get(contract => contract.OrganizationId == organizationId); - - //if (!string.IsNullOrEmpty(q)) pagingModel.Where(contract => contract.Name.Contains(q)); - //var contracts = Page(Repository.AsQueryable(), pagingModel); - - var overviewDtos = AutoMapper.Mapper.Map>(contracts); - var roles = _roleRepository.Get().ToList(); - var list = new List(); - var header = new ExpandoObject() as IDictionary; - header.Add("Aktiv", "Aktiv"); - header.Add("It Kontrakt", "It Kontrakt"); - header.Add("OrgUnit", "Ansv. organisationsenhed"); - header.Add("Underskriver", "KontraktUnderskriver"); - foreach (var role in roles) - header.Add(role.Name, role.Name); - header.Add("Leverandor", "Leverandør"); - header.Add("Anskaffelse", "Anskaffelse"); - header.Add("driftar", "Drift/år"); - header.Add("Betalingsmodel", "Betalingsmodel"); - header.Add("Audit", "Audit"); - list.Add(header); - foreach (var contract in overviewDtos) - { - var obj = new ExpandoObject() as IDictionary; - obj.Add("Aktiv", contract.IsActive); - obj.Add("It Kontrakt", contract.Name); - obj.Add("OrgUnit", contract.ResponsibleOrganizationUnitName); - foreach (var role in roles) - { - var roleId = role.Id; - obj.Add(role.Name, - String.Join(",", contract.Rights.Where(x => x.RoleId == roleId).Select(x => x.User.FullName))); - } - obj.Add("Leverandor", contract.SupplierName); - obj.Add("Anskaffelse", contract.AcquisitionSum); - obj.Add("driftar", contract.OperationSum); - obj.Add("Betalingsmodel", contract.PaymentModelName); - obj.Add("Audit", contract.FirstAuditDate); - list.Add(obj); - } - var s = list.ToCsv(); - var bytes = Encoding.Unicode.GetBytes(s); - var stream = new MemoryStream(); - stream.Write(bytes, 0, bytes.Length); - stream.Seek(0, SeekOrigin.Begin); - - var result = new HttpResponseMessage(HttpStatusCode.OK); - result.Content = new StreamContent(stream); - result.Content.Headers.ContentType = new MediaTypeHeaderValue("text/csv"); - result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileNameStar = "itkontraktoverblikokonomi.csv", DispositionType = "ISO-8859-1" }; - return result; - } - catch (Exception e) - { - return LogError(e); - } - } - + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] + [SwaggerResponse(HttpStatusCode.Forbidden)] public HttpResponseMessage GetPlan(bool? plan, int organizationId, [FromUri] PagingModel pagingModel, [FromUri] string q) { if (KitosUser.DefaultOrganizationId != organizationId) { - return Unauthorized(); + return Forbidden(); } try @@ -363,70 +333,6 @@ public HttpResponseMessage GetPlan(bool? plan, int organizationId, [FromUri] Pag } } - public HttpResponseMessage GetExcelPlan(bool? csvplan, int organizationId) - { - try - { - //Get contracts within organization - var contracts = Repository.Get(contract => contract.OrganizationId == organizationId); - - //if (!string.IsNullOrEmpty(q)) pagingModel.Where(contract => contract.Name.Contains(q)); - //var contracts = Page(Repository.AsQueryable(), pagingModel); - - var overviewDtos = AutoMapper.Mapper.Map>(contracts); - - var list = new List(); - var header = new ExpandoObject() as IDictionary; - header.Add("Aktiv", "Aktiv"); - header.Add("It Kontrakt", "It Kontrakt"); - header.Add("Type", "Kontrakttype"); - header.Add("Skabelon", "Kontraktskabelon"); - header.Add("Pur", "Indkøbsform"); - header.Add("Indgaet", "Indgået"); - header.Add("Varighed", "Varighed"); - header.Add("Udlobsdato", "Udløbsdato"); - header.Add("Option", "Option"); - header.Add("Opsigelse", "Opsigelse"); - header.Add("Uopsigelig", "Uopsigelig til"); - header.Add("Udbudsstrategi", "Udbudsstrategi"); - header.Add("Udbudsplan", "Udbudsplan"); - list.Add(header); - foreach (var contract in overviewDtos) - { - var obj = new ExpandoObject() as IDictionary; - obj.Add("Aktiv", contract.IsActive); - obj.Add("It Kontrakt", contract.Name); - obj.Add("Type", contract.ContractTypeName); - obj.Add("Skabelon", contract.ContractTemplateName); - obj.Add("Pur", contract.PurchaseFormName); - obj.Add("Indgaet", contract.Concluded); - obj.Add("Varighed", contract.Duration); - obj.Add("Udlobsdato", contract.ExpirationDate); - obj.Add("Option", contract.OptionExtendName); - obj.Add("Opsigelse", contract.TerminationDeadlineName); - obj.Add("Uopsigelig", contract.IrrevocableTo); - obj.Add("Udbudsstrategi", contract.ProcurementStrategyName); - obj.Add("Udbudsplan", contract.ProcurementPlanHalf + " | " + contract.ProcurementPlanYear); - list.Add(obj); - } - var s = list.ToCsv(); - var bytes = Encoding.Unicode.GetBytes(s); - var stream = new MemoryStream(); - stream.Write(bytes, 0, bytes.Length); - stream.Seek(0, SeekOrigin.Begin); - - var result = new HttpResponseMessage(HttpStatusCode.OK); - result.Content = new StreamContent(stream); - result.Content.Headers.ContentType = new MediaTypeHeaderValue("text/csv"); - result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileNameStar = "itkontraktoverbliktid.csv", DispositionType = "ISO-8859-1" }; - return result; - } - catch (Exception e) - { - return LogError(e); - } - } - private IEnumerable MapSystemUsages(ItContract contract) { return Map, IEnumerable>(contract.AssociatedSystemUsages.Select(x => x.ItSystemUsage)); @@ -439,9 +345,6 @@ protected override void DeleteQuery(ItContract entity) protected override bool HasWriteAccess(ItContract obj, User user, int organizationId) { - //if readonly - if (user.IsReadOnly && !user.IsGlobalAdmin) - return false; // local admin have write access if the obj is in context if (obj.IsInContext(organizationId) && user.OrganizationRights.Any(x => x.OrganizationId == organizationId && (x.Role == OrganizationRole.LocalAdmin || x.Role == OrganizationRole.ContractModuleAdmin))) diff --git a/Presentation.Web/Controllers/API/ItContractItSystemUsageController.cs b/Presentation.Web/Controllers/API/ItContractItSystemUsageController.cs index 6fb89bed89..8647e1dddf 100644 --- a/Presentation.Web/Controllers/API/ItContractItSystemUsageController.cs +++ b/Presentation.Web/Controllers/API/ItContractItSystemUsageController.cs @@ -2,9 +2,11 @@ using Core.DomainModel.ItContract; using Core.DomainModel.ItSystemUsage; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.API { + [PublicApi] public class ItContractItSystemUsageController : BaseApiController { private readonly IGenericRepository _repository; diff --git a/Presentation.Web/Controllers/API/ItContractRightsController.cs b/Presentation.Web/Controllers/API/ItContractRightsController.cs index a8017d2849..15eb109167 100644 --- a/Presentation.Web/Controllers/API/ItContractRightsController.cs +++ b/Presentation.Web/Controllers/API/ItContractRightsController.cs @@ -1,19 +1,15 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using Core.DomainModel.ItContract; +using Core.DomainModel.ItContract; using Core.DomainServices; -using Presentation.Web.Models; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.API { + [PublicApi] public class ItContractRightController : GenericRightsController { - private readonly IGenericRepository _objectRepository; - public ItContractRightController(IGenericRepository rightRepository, IGenericRepository objectRepository) : base(rightRepository, objectRepository) + public ItContractRightController(IGenericRepository rightRepository, IGenericRepository objectRepository) + : base(rightRepository, objectRepository) { - _objectRepository = objectRepository; } } } diff --git a/Presentation.Web/Controllers/API/ItContractRoleController.cs b/Presentation.Web/Controllers/API/ItContractRoleController.cs index 18199ee145..280dbe2cc8 100644 --- a/Presentation.Web/Controllers/API/ItContractRoleController.cs +++ b/Presentation.Web/Controllers/API/ItContractRoleController.cs @@ -1,9 +1,11 @@ using Core.DomainModel.ItContract; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class ItContractRoleController : GenericOptionApiController { public ItContractRoleController(IGenericRepository repository) diff --git a/Presentation.Web/Controllers/API/ItInterfaceController.cs b/Presentation.Web/Controllers/API/ItInterfaceController.cs index ae524006cd..ee922e93ea 100644 --- a/Presentation.Web/Controllers/API/ItInterfaceController.cs +++ b/Presentation.Web/Controllers/API/ItInterfaceController.cs @@ -1,24 +1,21 @@ using System; using System.Collections.Generic; -using System.Dynamic; -using System.IO; using System.Linq; using System.Net; using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; using System.Web.Http; using Core.ApplicationServices; using Core.DomainModel; using Core.DomainModel.ItSystem; using Core.DomainServices; using Newtonsoft.Json.Linq; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.API { - using Core.DomainModel.Organization; - + [PublicApi] public class ItInterfaceController : GenericContextAwareApiController { private readonly IItInterfaceService _itInterfaceService; @@ -56,6 +53,7 @@ protected override void DeleteQuery(ItInterface entity) _itInterfaceService.Delete(entity.Id); } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetSearch(string q, int orgId) { try @@ -86,6 +84,7 @@ public HttpResponseMessage GetSearch(string q, int orgId) } } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetCatalog(string q, int organizationId, [FromUri] PagingModel pagingModel) { try @@ -118,112 +117,6 @@ public HttpResponseMessage GetCatalog(string q, int organizationId, [FromUri] Pa } } - public HttpResponseMessage GetExcel(bool? csv, int organizationId) - { - try - { - var interfaces = Repository.Get( - x => - // global admin sees all within the context - KitosUser.IsGlobalAdmin && - x.OrganizationId == organizationId || - // object owner sees his own objects - x.ObjectOwnerId == KitosUser.Id || - // it's public everyone can see it - x.AccessModifier == AccessModifier.Public || - // everyone in the same organization can see normal objects - x.AccessModifier == AccessModifier.Local && - x.OrganizationId == organizationId - // it systems doesn't have roles so private doesn't make sense - // only object owners will be albe to see private objects - ); - var dtos = Map(interfaces); - - var list = new List(); - var header = new ExpandoObject() as IDictionary; - header.Add("Snitflade", "Snitflade"); - header.Add("Public", "(P)"); - header.Add("Snitfladetype", "Snitfladetype"); - header.Add("Interface", "Grænseflade"); - header.Add("Metode", "Metode"); - header.Add("TSA", "TSA"); - header.Add("Udstillet af", "Udstillet af"); - header.Add("Rettighedshaver", "Rettighedshaver"); - header.Add("Oprettet af", "Oprettet af"); - list.Add(header); - foreach (var itInterface in dtos) - { - var obj = new ExpandoObject() as IDictionary; - obj.Add("Snitflade", itInterface.Name); - obj.Add("Public", itInterface.AccessModifier == AccessModifier.Public ? "(P)" : ""); - obj.Add("Snitfladetype", itInterface.InterfaceTypeName); - obj.Add("Interface", itInterface.InterfaceName); - obj.Add("Metode", itInterface.MethodName); - obj.Add("TSA", itInterface.TsaName); - obj.Add("Udstillet af", itInterface.ExhibitedByItSystemName); - obj.Add("Rettighedshaver", itInterface.BelongsToName); - obj.Add("Oprettet af", itInterface.OrganizationName); - list.Add(obj); - } - var s = list.ToCsv(); - var bytes = Encoding.Unicode.GetBytes(s); - var stream = new MemoryStream(); - stream.Write(bytes, 0, bytes.Length); - stream.Seek(0, SeekOrigin.Begin); - - var result = new HttpResponseMessage(HttpStatusCode.OK); - result.Content = new StreamContent(stream); - result.Content.Headers.ContentType = new MediaTypeHeaderValue("text/csv"); - result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileNameStar = "snitfladekatalog.csv", DispositionType = "ISO-8859-1" }; - return result; - } - catch (Exception e) - { - return LogError(e); - } - } - - /// - /// Get interfaces by name that aren't already used by the system in question - /// - /// - /// - /// - /// Available interfaces - // - // Udkommenteret ifm. OS2KITOS-663 - //public HttpResponseMessage GetSearchExclude(string q, int orgId, int sysId) - //{ - // try - // { - // var interfaces = Repository.Get( - // s => - // // filter by name - // s.Name.Contains(q) && - // // filter (remove) interfaces already used by the system - // s.CanBeUsedBy.Count(x => x.ItSystemId == sysId) == 0 && - // // global admin sees all within the context - // (KitosUser.IsGlobalAdmin && - // s.OrganizationId == orgId || - // // object owner sees his own objects - // s.ObjectOwnerId == KitosUser.Id || - // // it's public everyone can see it - // s.AccessModifier == AccessModifier.Public || - // // everyone in the same organization can see normal objects - // s.AccessModifier == AccessModifier.Local && - // s.OrganizationId == orgId) - // // it systems doesn't have roles so private doesn't make sense - // // only object owners will be albe to see private objects - // ); - // var dtos = Map(interfaces); - // return Ok(dtos); - // } - // catch (Exception e) - // { - // return LogError(e); - // } - //} - public override HttpResponseMessage Post(ItInterfaceDTO dto) { try @@ -269,6 +162,8 @@ public override HttpResponseMessage Patch(int id, int organizationId, JObject ob return base.Patch(id, organizationId, obj); } + [SwaggerResponse(HttpStatusCode.OK)] + [SwaggerResponse(HttpStatusCode.Conflict, Description = "It Interface name must be new")] public HttpResponseMessage GetNameAvailable(string checkname, int orgId) { try @@ -281,6 +176,8 @@ public HttpResponseMessage GetNameAvailable(string checkname, int orgId) } } + [SwaggerResponse(HttpStatusCode.OK)] + [SwaggerResponse(HttpStatusCode.Conflict, Description = "It Interface Id and name must be unique")] public HttpResponseMessage GetItInterfaceNameUniqueConstraint(string checkitinterfaceid, string checkname, int orgId) { try diff --git a/Presentation.Web/Controllers/API/ItInterfaceExhibitUsageController.cs b/Presentation.Web/Controllers/API/ItInterfaceExhibitUsageController.cs index dce65f8c7e..7901ed7745 100644 --- a/Presentation.Web/Controllers/API/ItInterfaceExhibitUsageController.cs +++ b/Presentation.Web/Controllers/API/ItInterfaceExhibitUsageController.cs @@ -1,13 +1,17 @@ using System; using System.Collections.Generic; +using System.Net; using System.Net.Http; using Core.DomainModel.ItSystemUsage; using Core.DomainServices; using Newtonsoft.Json.Linq; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.API { + [PublicApi] public class ItInterfaceExhibitUsageController : BaseApiController { private readonly IGenericRepository _repository; @@ -17,6 +21,7 @@ public ItInterfaceExhibitUsageController(IGenericRepository>))] public HttpResponseMessage GetByContract(int contractId) { try @@ -32,6 +37,7 @@ public HttpResponseMessage GetByContract(int contractId) } } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO))] public HttpResponseMessage GetSingle(int usageId, int exhibitId) { try diff --git a/Presentation.Web/Controllers/API/ItInterfaceUsageController.cs b/Presentation.Web/Controllers/API/ItInterfaceUsageController.cs index 5862db61e6..0b94af5018 100644 --- a/Presentation.Web/Controllers/API/ItInterfaceUsageController.cs +++ b/Presentation.Web/Controllers/API/ItInterfaceUsageController.cs @@ -1,13 +1,17 @@ using System; using System.Collections.Generic; +using System.Net; using System.Net.Http; using Core.DomainModel.ItSystemUsage; using Core.DomainServices; using Newtonsoft.Json.Linq; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.API { + [PublicApi] public class ItInterfaceUsageController : BaseApiController { private readonly IGenericRepository _repository; @@ -17,6 +21,7 @@ public ItInterfaceUsageController(IGenericRepository repositor _repository = repository; } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage Get(int usageId, int sysId, int interfaceId) { try @@ -31,6 +36,7 @@ public HttpResponseMessage Get(int usageId, int sysId, int interfaceId) } } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetByUsage(int usageId) { try @@ -45,6 +51,7 @@ public HttpResponseMessage GetByUsage(int usageId) } } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetByContract(int contractId) { try diff --git a/Presentation.Web/Controllers/API/ItProjectController.cs b/Presentation.Web/Controllers/API/ItProjectController.cs index 50968b6acb..f95a5d0206 100644 --- a/Presentation.Web/Controllers/API/ItProjectController.cs +++ b/Presentation.Web/Controllers/API/ItProjectController.cs @@ -1,12 +1,8 @@ using System; using System.Collections.Generic; -using System.Dynamic; -using System.IO; using System.Linq; using System.Net; using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; using System.Web.Http; using Core.ApplicationServices; using Core.DomainModel; @@ -15,16 +11,18 @@ using Core.DomainModel.Organization; using Core.DomainServices; using Newtonsoft.Json.Linq; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.API { + [PublicApi] public class ItProjectController : GenericHierarchyApiController { private readonly IItProjectService _itProjectService; private readonly IGenericRepository _taskRepository; private readonly IGenericRepository _itSystemUsageRepository; - private readonly IGenericRepository _roleRepository; private readonly IGenericRepository _orgUnitRepository; //TODO: Man, this constructor smells ... @@ -33,17 +31,22 @@ public ItProjectController( IItProjectService itProjectService, IGenericRepository orgUnitRepository, IGenericRepository taskRepository, - IGenericRepository itSystemUsageRepository, - IGenericRepository roleRepository) + IGenericRepository itSystemUsageRepository) : base(repository) { _itProjectService = itProjectService; _taskRepository = taskRepository; _itSystemUsageRepository = itSystemUsageRepository; - _roleRepository = roleRepository; _orgUnitRepository = orgUnitRepository; } + /// + /// Henter alle IT-Projekter i organisationen samt offentlige IT-projekter fra andre organisationer + /// + /// + /// + /// + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetByOrg([FromUri] int orgId, [FromUri] PagingModel pagingModel) { try @@ -72,9 +75,14 @@ public HttpResponseMessage GetByOrg([FromUri] int orgId, [FromUri] PagingModel - /// Accessmodifier is and should always be 0 since it is not allowed to be accessed outside the organisation - /// + + /// + /// Henter alle IT-Projekter i organisationen samt offentlige IT-projekter fra andre organisationer + /// + /// + /// + /// + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public virtual HttpResponseMessage Get(string q, int orgId) { try @@ -103,6 +111,7 @@ public virtual HttpResponseMessage Get(string q, int orgId) } } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetHierarchy(int id, [FromUri] bool? hierarchy) { try @@ -126,6 +135,7 @@ public HttpResponseMessage GetHierarchy(int id, [FromUri] bool? hierarchy) } } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetOverview(bool? overview, [FromUri] int orgId, [FromUri] string q, [FromUri] PagingModel pagingModel) { try @@ -157,108 +167,15 @@ public HttpResponseMessage GetOverview(bool? overview, [FromUri] int orgId, [Fro } } - public HttpResponseMessage GetExcel(bool? csv, [FromUri] int orgId) - { - try - { - //Get all projects inside the organizaton - var projects = Repository.Get(p => p.OrganizationId == orgId); - - //if (!string.IsNullOrEmpty(q)) pagingModel.Where(proj => proj.Name.Contains(q)); - //var projects = Page(Repository.AsQueryable(), pagingModel); - - var dtos = Map, IEnumerable>(projects); - - var roles = _roleRepository.Get().ToList(); - - var list = new List(); - var header = new ExpandoObject() as IDictionary; - header.Add("Arkiveret", "Arkiveret"); - header.Add("Name", "It Projekt"); - header.Add("OrgUnit", "Ansv. organisationsenhed"); - foreach (var role in roles) - header.Add(role.Name, role.Name); - header.Add("ID", "Projekt ID"); - header.Add("Type", "Type"); - header.Add("Strategisk", "Strategisk"); - header.Add("Tværgaaende", "Tværgående"); - header.Add("Fase", "Fase"); - header.Add("Status", "Status projekt"); - header.Add("Maal", "Status mål"); - header.Add("Risiko", "Risiko"); - header.Add("RO", "RO"); - header.Add("Okonomi", "Økonomi"); - header.Add("P1", "Prioritet 1"); - header.Add("P2", "Prioritet 2"); - list.Add(header); - foreach (var project in dtos) - { - var obj = new ExpandoObject() as IDictionary; - obj.Add("Arkiveret", project.IsArchived); - obj.Add("Name", project.Name); - obj.Add("OrgUnit", project.ResponsibleOrgUnitName); - - foreach (var role in roles) - { - var roleId = role.Id; - obj.Add(role.Name, - String.Join(",", project.Rights.Where(x => x.RoleId == roleId).Select(x => x.User.FullName))); - } - obj.Add("ID", project.ItProjectId); - obj.Add("Type", project.ItProjectTypeName); - obj.Add("Strategisk", project.IsStrategy); - obj.Add("Tværgaaende", project.IsTransversal); - - switch (project.CurrentPhase) - { - case 1: - obj.Add("Fase", project.Phase1.Name); - break; - case 2: - obj.Add("Fase", project.Phase2.Name); - break; - case 3: - obj.Add("Fase", project.Phase3.Name); - break; - case 4: - obj.Add("Fase", project.Phase4.Name); - break; - case 5: - obj.Add("Fase", project.Phase5.Name); - break; - default: - obj.Add("Fase", "Ikke sat"); - break; - } - - obj.Add("Status", project.StatusProject); - obj.Add("Maal", project.GoalStatusStatus); - obj.Add("Risiko", project.AverageRisk); - obj.Add("RO", project.Roi); - obj.Add("Okonomi", project.Bc); - obj.Add("P1", project.Priority); - obj.Add("P2", project.PriorityPf); - list.Add(obj); - } - - var s = list.ToCsv(); - var bytes = Encoding.Unicode.GetBytes(s); - var stream = new MemoryStream(); - stream.Write(bytes, 0, bytes.Length); - stream.Seek(0, SeekOrigin.Begin); - - var result = new HttpResponseMessage(HttpStatusCode.OK); - result.Content = new StreamContent(stream); - result.Content.Headers.ContentType = new MediaTypeHeaderValue("text/csv"); - result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileNameStar = "itprojektoversigt.csv", DispositionType = "ISO-8859-1" }; - return result; - } - catch (Exception e) - { - return LogError(e); - } - } - + /// + /// Henter alle IT-Projekter i organisationen samt offentlige IT-projekter fra andre organisationer + /// + /// + /// + /// + /// + /// + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetCatalog(bool? catalog, [FromUri] int orgId, [FromUri] string q, [FromUri] PagingModel pagingModel) { try @@ -279,77 +196,7 @@ public HttpResponseMessage GetCatalog(bool? catalog, [FromUri] int orgId, [FromU } } - public HttpResponseMessage GetExcelCat(bool? csvcat, [FromUri] int orgId, [FromUri] string q, [FromUri] PagingModel pagingModel) - { - try - { - //Get all projects inside the organizaton OR public - pagingModel.Where(p => p.OrganizationId == orgId || p.AccessModifier == AccessModifier.Public); - if (!string.IsNullOrEmpty(q)) pagingModel.Where(proj => proj.Name.Contains(q)); - - var projects = Page(Repository.AsQueryable(), pagingModel); - - var dtos = Map, IEnumerable>(projects); - - var list = new List(); - var header = new ExpandoObject() as IDictionary; - header.Add("Name", "It Projekt"); - header.Add("Org", "Oprettet af: Organisation"); - header.Add("Navn", "Oprettet af: Navn"); - header.Add("ID", "Projekt ID"); - header.Add("Type", "Type"); - header.Add("Public", "Public"); - header.Add("Arkiv", "Arkiv"); - list.Add(header); - foreach (var project in dtos) - { - var obj = new ExpandoObject() as IDictionary; - obj.Add("Name", project.Name); - obj.Add("Org", project.OrganizationName); - obj.Add("Navn", project.ObjectOwnerName); - obj.Add("ID", project.ItProjectId); - obj.Add("Type", project.ItProjectTypeName); - obj.Add("Public", project.AccessModifier); - obj.Add("Arkiv", project.IsArchived); - list.Add(obj); - } - - var s = list.ToCsv(); - var bytes = Encoding.Unicode.GetBytes(s); - var stream = new MemoryStream(); - stream.Write(bytes, 0, bytes.Length); - stream.Seek(0, SeekOrigin.Begin); - - var result = new HttpResponseMessage(HttpStatusCode.OK); - result.Content = new StreamContent(stream); - result.Content.Headers.ContentType = new MediaTypeHeaderValue("text/csv"); - result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileNameStar = "itprojektoversigt.csv", DispositionType = "ISO-8859-1" }; - return result; - } - catch (Exception e) - { - return LogError(e); - } - } - - // TODO cloning has been disabled for now, reviewed at a later date - //public HttpResponseMessage PostCloneProject(int id, bool? clone, [FromBody] ItProjectDTO dto) - //{ - // try - // { - // //make sure we only clone projects that the is accessible from the organization - // var project = _itProjectService.GetAll(dto.OrganizationId).FirstOrDefault(p => p.Id == id); - - // var clonedProject = _itProjectService.CloneProject(project, KitosUser, dto.OrganizationId); - - // return Created(Map(clonedProject), new Uri(Request.RequestUri + "/" + clonedProject.Id)); - // } - // catch (Exception e) - // { - // return Error(e); - // } - //} - + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetProjectsByType([FromUri] int orgId, [FromUri] int typeId) { try @@ -364,12 +211,16 @@ public HttpResponseMessage GetProjectsByType([FromUri] int orgId, [FromUri] int } } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetOrganizationUnitsUsingThisProject(int id, [FromUri] int organizationUnit) { try { var project = Repository.GetByKey(id); - if (project == null) return NotFound(); + if (project == null) + { + return NotFound(); + } var dtos = Map, IEnumerable>( @@ -388,12 +239,21 @@ public HttpResponseMessage PostOrganizationUnitsUsingThisProject(int id, int org try { var project = Repository.GetByKey(id); - if (project == null) return NotFound(); + if (project == null) + { + return NotFound(); + } - if (!HasWriteAccess(project, organizationId)) return Unauthorized(); + if (!HasWriteAccess(project, organizationId)) + { + return Forbidden(); + } var orgUnit = _orgUnitRepository.GetByKey(organizationUnit); - if (orgUnit == null) return NotFound(); + if (orgUnit == null) + { + return NotFound(); + } project.UsedByOrgUnits.Add(new ItProjectOrgUnitUsage {ItProjectId = id, OrganizationUnitId = organizationUnit}); @@ -422,12 +282,21 @@ public HttpResponseMessage DeleteOrganizationUnitsUsingThisProject(int id, int o try { var project = Repository.GetByKey(id); - if (project == null) return NotFound(); + if (project == null) + { + return NotFound(); + } - if (!HasWriteAccess(project, organizationId)) return Unauthorized(); + if (!HasWriteAccess(project, organizationId)) + { + return Forbidden(); + } var entity = project.UsedByOrgUnits.SingleOrDefault(x => x.ItProjectId == id && x.OrganizationUnitId == organizationUnit); - if (entity == null) return NotFound(); + if (entity == null) + { + return NotFound(); + } project.UsedByOrgUnits.Remove(entity); project.LastChanged = DateTime.UtcNow; @@ -448,8 +317,14 @@ public HttpResponseMessage PostTaskToProject(int id, int organizationId, [FromUr try { var project = Repository.GetByKey(id); - if (project == null) return NotFound(); - if (!HasWriteAccess(project, organizationId)) return Unauthorized(); + if (project == null) + { + return NotFound(); + } + if (!HasWriteAccess(project, organizationId)) + { + return Forbidden(); + } List tasks; if (taskId.HasValue) @@ -472,7 +347,9 @@ public HttpResponseMessage PostTaskToProject(int id, int organizationId, [FromUr } if (!tasks.Any()) + { return NotFound(); + } foreach (var task in tasks) { @@ -494,8 +371,15 @@ public HttpResponseMessage DeleteTaskToProject(int id, int organizationId, [From try { var project = Repository.GetByKey(id); - if (project == null) return NotFound(); - if (!HasWriteAccess(project, organizationId)) return Unauthorized(); + if (project == null) + { + return NotFound(); + } + + if (!HasWriteAccess(project, organizationId)) + { + return Forbidden(); + } List tasks; if (taskId.HasValue) @@ -514,7 +398,9 @@ public HttpResponseMessage DeleteTaskToProject(int id, int organizationId, [From } if (!tasks.Any()) + { return NotFound(); + } foreach (var task in tasks) { @@ -540,6 +426,7 @@ public HttpResponseMessage DeleteTaskToProject(int id, int organizationId, [From /// Optional filtering on task group /// Paging model /// List of TaskRefSelectedDTO + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetTasks(int id, bool? tasks, bool onlySelected, int? taskGroup, [FromUri] PagingModel pagingModel) { try @@ -581,6 +468,7 @@ public HttpResponseMessage GetTasks(int id, bool? tasks, bool onlySelected, int? } } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetItSystemsUsedByThisProject(int id, [FromUri] bool? usages) { try @@ -601,13 +489,22 @@ public HttpResponseMessage PostItSystemsUsedByThisProject(int id, int organizati try { var project = Repository.GetByKey(id); - if (project == null) return NotFound(); + if (project == null) + { + return NotFound(); + } - if (!HasWriteAccess(project, organizationId)) return Unauthorized(); + if (!HasWriteAccess(project, organizationId)) + { + return Forbidden(); + } //TODO: should also we check for write access to the system usage? var systemUsage = _itSystemUsageRepository.GetByKey(usageId); - if (systemUsage == null) return NotFound(); + if (systemUsage == null) + { + return NotFound(); + } project.ItSystemUsages.Add(systemUsage); @@ -629,14 +526,21 @@ public HttpResponseMessage DeleteItSystemsUsedByThisProject(int id, int organiza try { var project = Repository.GetByKey(id); - if (project == null) return NotFound(); + if (project == null) + { + return NotFound(); + } if (!HasWriteAccess(project, organizationId)) - return Unauthorized(); + { + return Forbidden(); + } var systemUsage = _itSystemUsageRepository.GetByKey(usageId); if (systemUsage == null) + { return NotFound(); + } project.ItSystemUsages.Remove(systemUsage); project.LastChanged = DateTime.UtcNow; @@ -657,6 +561,8 @@ public HttpResponseMessage DeleteItSystemsUsedByThisProject(int id, int organiza /// /// /// + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] + [SwaggerResponse(HttpStatusCode.NotFound)] public HttpResponseMessage GetItProjectsUsedByOrg([FromUri] int orgId, [FromUri] bool itProjects) { try @@ -677,6 +583,7 @@ public HttpResponseMessage GetItProjectsUsedByOrg([FromUri] int orgId, [FromUri] /// /// /// + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetItProjectsUsedByOrg([FromUri] int orgId, [FromUri] int usageId) { try @@ -712,7 +619,7 @@ public override HttpResponseMessage Post(ItProjectDTO dto) // only global admin can set access mod to public if (dto.AccessModifier == AccessModifier.Public && !FeatureChecker.CanExecute(KitosUser, Feature.CanSetAccessModifierToPublic)) { - return Unauthorized(); + return Forbidden(); } //force set access modifier to 0 dto.AccessModifier = 0; @@ -723,8 +630,14 @@ public HttpResponseMessage PostPhaseChange(int id, int organizationId, string ph { var project = Repository.GetByKey(id); - if (project == null) return NotFound(); - if (!HasWriteAccess(project, organizationId)) return Unauthorized(); + if (project == null) + { + return NotFound(); + } + if (!HasWriteAccess(project, organizationId)) + { + return Forbidden(); + } const string propertyName = "Phase"; var phaseRef = project.GetType().GetProperty(propertyName + phaseNum); @@ -776,16 +689,13 @@ public override HttpResponseMessage Patch(int id, int organizationId, JObject ob if (accessModToken != null && accessModToken.ToObject() == AccessModifier.Public && !FeatureChecker.CanExecute(KitosUser, Feature.CanSetAccessModifierToPublic)) { - return Unauthorized(); + return Forbidden(); } return base.Patch(id, organizationId, obj); } protected override bool HasWriteAccess(ItProject obj, User user, int organizationId) { - //if readonly - if (user.IsReadOnly && !user.IsGlobalAdmin) - return false; // local admin have write access if the obj is in context if (obj.IsInContext(organizationId) && user.OrganizationRights.Any(x => x.OrganizationId == organizationId && (x.Role == OrganizationRole.LocalAdmin || x.Role == OrganizationRole.ProjectModuleAdmin))) diff --git a/Presentation.Web/Controllers/API/ItProjectOrgUnitUsageController.cs b/Presentation.Web/Controllers/API/ItProjectOrgUnitUsageController.cs index f2b76c498f..e33abc1e67 100644 --- a/Presentation.Web/Controllers/API/ItProjectOrgUnitUsageController.cs +++ b/Presentation.Web/Controllers/API/ItProjectOrgUnitUsageController.cs @@ -1,14 +1,18 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Net.Http; using AutoMapper; using Core.DomainModel.ItProject; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.API { + [PublicApi] public class ItProjectOrgUnitUsageController : BaseApiController { private readonly IGenericRepository _responsibleOrgUnitRepository; @@ -20,6 +24,7 @@ public ItProjectOrgUnitUsageController(IGenericRepository _projectRepository = projectRepository; } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetOrgUnitsByProject(int id) { try @@ -36,6 +41,7 @@ public HttpResponseMessage GetOrgUnitsByProject(int id) } } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO))] public HttpResponseMessage GetResponsibleByProject(int id, bool? responsible) { try diff --git a/Presentation.Web/Controllers/API/ItProjectRightController.cs b/Presentation.Web/Controllers/API/ItProjectRightController.cs index f521f85931..ea7e83054a 100644 --- a/Presentation.Web/Controllers/API/ItProjectRightController.cs +++ b/Presentation.Web/Controllers/API/ItProjectRightController.cs @@ -1,12 +1,16 @@ using System; using System.Collections.Generic; +using System.Net; using System.Net.Http; using Core.DomainModel.ItProject; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.API { + [PublicApi] public class ItProjectRightController : GenericRightsController { public ItProjectRightController(IGenericRepository rightRepository, IGenericRepository objectRepository) : base(rightRepository, objectRepository) @@ -18,6 +22,7 @@ public ItProjectRightController(IGenericRepository rightReposito /// /// Id of the user /// List of rights + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetRightsForUser(int userId) { try diff --git a/Presentation.Web/Controllers/API/ItProjectRoleController.cs b/Presentation.Web/Controllers/API/ItProjectRoleController.cs index 131287a460..16199ccb0f 100644 --- a/Presentation.Web/Controllers/API/ItProjectRoleController.cs +++ b/Presentation.Web/Controllers/API/ItProjectRoleController.cs @@ -1,9 +1,11 @@ using Core.DomainModel.ItProject; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class ItProjectRoleController : GenericOptionApiController { public ItProjectRoleController(IGenericRepository repository) diff --git a/Presentation.Web/Controllers/API/ItProjectStatusController.cs b/Presentation.Web/Controllers/API/ItProjectStatusController.cs index 872bc3147c..de08e5ece1 100644 --- a/Presentation.Web/Controllers/API/ItProjectStatusController.cs +++ b/Presentation.Web/Controllers/API/ItProjectStatusController.cs @@ -1,13 +1,18 @@ using System; +using System.Collections.Generic; using System.Linq; +using System.Net; using System.Net.Http; using System.Web.Http; using Core.DomainModel.ItProject; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.API { + [PublicApi] public class ItProjectStatusController : GenericContextAwareApiController { public ItProjectStatusController(IGenericRepository repository) @@ -15,6 +20,7 @@ public ItProjectStatusController(IGenericRepository repository) { } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetByProject(int id, [FromUri] bool? project, [FromUri] PagingModel paging) { try diff --git a/Presentation.Web/Controllers/API/ItProjectTypeController.cs b/Presentation.Web/Controllers/API/ItProjectTypeController.cs index 4b762d53b6..d572f4b546 100644 --- a/Presentation.Web/Controllers/API/ItProjectTypeController.cs +++ b/Presentation.Web/Controllers/API/ItProjectTypeController.cs @@ -1,9 +1,11 @@ using Core.DomainModel.ItProject; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class ItProjectTypeController : GenericOptionApiController { public ItProjectTypeController(IGenericRepository repository) diff --git a/Presentation.Web/Controllers/API/ItSystemCategorieController.cs b/Presentation.Web/Controllers/API/ItSystemCategorieController.cs index cecc4b78f5..3020985314 100644 --- a/Presentation.Web/Controllers/API/ItSystemCategorieController.cs +++ b/Presentation.Web/Controllers/API/ItSystemCategorieController.cs @@ -1,17 +1,16 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; +using Core.ApplicationServices.Authorization; using Core.DomainModel.ItSystem; using Core.DomainModel.ItSystemUsage; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class ItSystemCategorieController : GenericOptionApiController { - ItSystemCategorieController(IGenericRepository repository) - : base(repository) { } + ItSystemCategorieController(IGenericRepository repository, IAuthorizationContext authorizationContext) + : base(repository, authorizationContext) { } } } \ No newline at end of file diff --git a/Presentation.Web/Controllers/API/ItSystemController.cs b/Presentation.Web/Controllers/API/ItSystemController.cs index d66da1cf92..7e0295f932 100644 --- a/Presentation.Web/Controllers/API/ItSystemController.cs +++ b/Presentation.Web/Controllers/API/ItSystemController.cs @@ -1,31 +1,37 @@ using System; using System.Collections.Generic; -using System.Dynamic; -using System.IO; using System.Linq; using System.Net; using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; using System.Web.Http; using Core.ApplicationServices; +using Core.ApplicationServices.Authorization; using Core.DomainModel; using Core.DomainModel.ItSystem; using Core.DomainModel.Organization; using Core.DomainServices; +using Core.DomainServices.Authorization; using Newtonsoft.Json.Linq; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.API { + [PublicApi] public class ItSystemController : GenericHierarchyApiController { private readonly IGenericRepository _taskRepository; private readonly IItSystemService _systemService; private readonly ReferenceService _referenceService; - public ItSystemController(IGenericRepository repository, IGenericRepository taskRepository, IItSystemService systemService, ReferenceService referenceService) - : base(repository) + public ItSystemController( + IGenericRepository repository, + IGenericRepository taskRepository, + IItSystemService systemService, + ReferenceService referenceService, + IAuthorizationContext authorizationContext) + : base(repository, authorizationContext) { _taskRepository = taskRepository; _systemService = systemService; @@ -63,6 +69,14 @@ protected override void DeleteQuery(ItSystem entity) _systemService.Delete(entity.Id); } + /// + /// Henter alle IT-Systemer i organisationen samt offentlige IT Systemer fra andre organisationer + /// + /// + /// + /// + /// + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetPublic([FromUri] int organizationId, [FromUri] PagingModel paging, [FromUri] string q) { try @@ -83,6 +97,7 @@ public HttpResponseMessage GetPublic([FromUri] int organizationId, [FromUri] Pag // it systems doesn't have roles so private doesn't make sense // only object owners will be albe to see private objects ); + paging.WithPostProcessingFilter(AllowRead); if (!string.IsNullOrEmpty(q)) paging.Where(sys => sys.Name.Contains(q)); @@ -96,74 +111,7 @@ public HttpResponseMessage GetPublic([FromUri] int organizationId, [FromUri] Pag } } - public HttpResponseMessage GetExcel([FromUri] bool? csv, [FromUri] int organizationId) - { - try - { - var systems = - Repository.AsQueryable() - .Where(s => - // global admin sees all - (KitosUser.IsGlobalAdmin || - // object owner sees his own objects - s.ObjectOwnerId == KitosUser.Id || - // it's public everyone can see it - s.AccessModifier == AccessModifier.Public || - // everyone in the same organization can see normal objects - s.AccessModifier == AccessModifier.Local && - s.OrganizationId == organizationId - // it systems doesn't have roles so private doesn't make sense - // only object owners will be albe to see private objects - )); - - //if (!string.IsNullOrEmpty(q)) paging.Where(sys => sys.Name.Contains(q)); - - //var query = Page(systems, paging); - - var dtos = Map(systems); - - var list = new List(); - var header = new ExpandoObject() as IDictionary; - header.Add("It System", "It System"); - header.Add("Public", "(P)"); - header.Add("AppTypeOption", "Applikationstype"); - header.Add("BusiType", "Forretningstype"); - header.Add("KLEID", "KLE ID"); - header.Add("KLENavn", "KLE Navn"); - header.Add("Rettighedshaver", "Rettighedshaver"); - header.Add("Oprettet", "Oprettet af"); - list.Add(header); - foreach (var system in dtos) - { - var obj = new ExpandoObject() as IDictionary; - obj.Add("It System", system.Name); - obj.Add("Public", system.AccessModifier == AccessModifier.Public ? "(P)" : ""); - obj.Add("AppType", system.AppTypeOptionName); - obj.Add("BusiType", system.BusinessTypeName); - obj.Add("KLEID", String.Join(",", system.TaskRefs.Select(x => x.TaskKey))); - obj.Add("KLENavn", String.Join(",", system.TaskRefs.Select(x => x.Description))); - obj.Add("Rettighedshaver", system.BelongsToName); - obj.Add("Oprettet", system.ObjectOwnerFullName); - list.Add(obj); - } - var csvList = list.ToCsv(); - var bytes = Encoding.Unicode.GetBytes(csvList); - var stream = new MemoryStream(); - stream.Write(bytes, 0, bytes.Length); - stream.Seek(0, SeekOrigin.Begin); - - var result = new HttpResponseMessage(HttpStatusCode.OK); - result.Content = new StreamContent(stream); - result.Content.Headers.ContentType = new MediaTypeHeaderValue("text/csv"); - result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileNameStar = "itsystemkatalog.csv", DispositionType = "ISO-8859-1" }; - return result; - } - catch (Exception e) - { - return LogError(e); - } - } - + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetInterfacesSearch(string q, int orgId, int excludeId) { try @@ -186,6 +134,9 @@ public HttpResponseMessage GetInterfacesSearch(string q, int orgId, int excludeI // it systems doesn't have roles so private doesn't make sense // only object owners will be albe to see private objects ); + + systems = systems.Where(AllowRead); + var dtos = Map(systems); return Ok(dtos); } @@ -195,11 +146,15 @@ public HttpResponseMessage GetInterfacesSearch(string q, int orgId, int excludeI } } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetInterfacesSearch(string q, int orgId, bool? interfaces) { try { var systems = _systemService.GetInterfaces(orgId, q, KitosUser); + + systems = systems.Where(AllowRead); + var dtos = Map(systems); return Ok(dtos); } @@ -209,11 +164,15 @@ public HttpResponseMessage GetInterfacesSearch(string q, int orgId, bool? interf } } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetNonInterfacesSearch(string q, int orgId, bool? nonInterfaces) { try { var systems = _systemService.GetNonInterfaces(orgId, q, KitosUser); + + systems = systems.Where(AllowRead); + var dtos = Map(systems); return Ok(dtos); } @@ -223,11 +182,15 @@ public HttpResponseMessage GetNonInterfacesSearch(string q, int orgId, bool? non } } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetHierarchy(int id, [FromUri] bool hierarchy) { try { var systems = _systemService.GetHierarchy(id); + + systems = systems.Where(AllowRead); + return Ok(Map(systems)); } catch (Exception e) @@ -240,21 +203,26 @@ public override HttpResponseMessage Post(ItSystemDTO dto) { try { - // only global admin can set access mod to public - if (dto.AccessModifier == AccessModifier.Public && !KitosUser.IsGlobalAdmin) - { - return Unauthorized(); - } - if (!IsAvailable(dto.Name, dto.OrganizationId)) + { return Conflict("Name is already taken!"); + } var item = Map(dto); + if (dto.AccessModifier == AccessModifier.Public && !AllowEntityVisibilityControl(item)) + { + return Forbidden(); + } item.ObjectOwner = KitosUser; item.LastChangedByUser = KitosUser; item.Uuid = Guid.NewGuid(); + if (!AllowCreate(item)) + { + return Forbidden(); + } + foreach (var id in dto.TaskRefIds) { var task = _taskRepository.GetByKey(id); @@ -271,12 +239,21 @@ public override HttpResponseMessage Post(ItSystemDTO dto) } } + /// + /// Henter alle IT Systemer ejet af organisationen samt IT Systemer fra andre organisationer som er anvendt i organisationen + /// + /// + /// + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] + [SwaggerResponse(HttpStatusCode.NotFound)] public HttpResponseMessage GetItSystemsUsedByOrg([FromUri] int orgId) { try { var systems = Repository.Get(x => x.OrganizationId == orgId || x.Usages.Any(y => y.OrganizationId == orgId)); + systems = systems?.Where(AllowRead); + return systems == null ? NotFound() : Ok(Map(systems)); } catch (Exception e) @@ -291,7 +268,10 @@ public HttpResponseMessage PostTasksUsedByThisSystem(int id, int organizationId, { var system = Repository.GetByKey(id); if (system == null) return NotFound(); - if (!HasWriteAccess(system, organizationId)) return Unauthorized(); + if (!AllowModify(system)) + { + return Forbidden(); + } List tasks; if (taskId.HasValue) @@ -336,8 +316,15 @@ public HttpResponseMessage DeleteTasksUsedByThisSystem(int id, int organizationI try { var system = Repository.GetByKey(id); - if (system == null) return NotFound(); - if (!HasWriteAccess(system, organizationId)) return Unauthorized(); + if (system == null) + { + return NotFound(); + } + + if (!AllowModify(system)) + { + return Forbidden(); + } List tasks; if (taskId.HasValue) @@ -382,11 +369,23 @@ public HttpResponseMessage DeleteTasksUsedByThisSystem(int id, int organizationI /// Optional filtering on task group /// Paging model /// List of TaskRefSelectedDTO + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] + [SwaggerResponse(HttpStatusCode.Forbidden)] + [SwaggerResponse(HttpStatusCode.NotFound)] public HttpResponseMessage GetTasks(int id, bool? tasks, bool onlySelected, int? taskGroup, [FromUri] PagingModel pagingModel) { try { var system = Repository.GetByKey(id); + if (system == null) + { + return NotFound(); + } + + if (!AllowRead(system)) + { + return Forbidden(); + } IQueryable taskQuery; if (onlySelected) @@ -407,6 +406,7 @@ public HttpResponseMessage GetTasks(int id, bool? tasks, bool onlySelected, int? else pagingModel.Where(taskRef => taskRef.Children.Count == 0); + pagingModel.WithPostProcessingFilter(AllowRead); var theTasks = Page(taskQuery, pagingModel).ToList(); var dtos = theTasks.Select(task => new TaskRefSelectedDTO() @@ -428,10 +428,11 @@ public override HttpResponseMessage Patch(int id, int organizationId, JObject ob // try get AccessModifier value JToken accessModToken; obj.TryGetValue("accessModifier", out accessModToken); - // only global admin can set access mod to public - if (accessModToken != null && accessModToken.ToObject() == AccessModifier.Public && !KitosUser.IsGlobalAdmin) + + var itSystem = Repository.GetByKey(id); + if (accessModToken != null && accessModToken.ToObject() == AccessModifier.Public && !AllowEntityVisibilityControl(itSystem)) { - return Unauthorized(); + return Forbidden(); } // try get name value @@ -448,10 +449,17 @@ public override HttpResponseMessage Patch(int id, int organizationId, JObject ob return base.Patch(id, organizationId, obj); } + [SwaggerResponse(HttpStatusCode.OK)] + [SwaggerResponse(HttpStatusCode.Forbidden)] + [SwaggerResponse(HttpStatusCode.Conflict, Description = "It System names must be new")] public HttpResponseMessage GetNameAvailable(string checkname, int orgId) { try { + if (GetOrganizationReadAccessLevel(orgId) == OrganizationDataReadAccessLevel.None) + { + return Forbidden(); + } return IsAvailable(checkname, orgId) ? Ok() : Conflict("Name is already taken!"); } catch (Exception e) @@ -465,23 +473,5 @@ private bool IsAvailable(string name, int orgId) var system = Repository.Get(x => x.Name == name && x.OrganizationId == orgId); return !system.Any(); } - - protected override bool HasWriteAccess(ItSystem obj, User user, int organizationId) - { - if (obj.IsInContext(organizationId) && user.OrganizationRights.Any(x => x.OrganizationId == organizationId && (x.Role == OrganizationRole.LocalAdmin || x.Role == OrganizationRole.SystemModuleAdmin))) - { - return true; - } - if (user.IsLocalAdmin && obj.ObjectOwnerId == user.Id && user.DefaultOrganizationId == organizationId) - { - return true; - } - return HasWriteAccess(); - } - - protected bool HasWriteAccess() - { - return KitosUser.IsGlobalAdmin; - } } } diff --git a/Presentation.Web/Controllers/API/ItSystemRoleController.cs b/Presentation.Web/Controllers/API/ItSystemRoleController.cs index f53faa6e6e..0c09536b38 100644 --- a/Presentation.Web/Controllers/API/ItSystemRoleController.cs +++ b/Presentation.Web/Controllers/API/ItSystemRoleController.cs @@ -1,13 +1,16 @@ -using Core.DomainModel.ItSystem; +using Core.ApplicationServices.Authorization; +using Core.DomainModel.ItSystem; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class ItSystemRoleController : GenericOptionApiController { - public ItSystemRoleController(IGenericRepository repository) - : base(repository) + public ItSystemRoleController(IGenericRepository repository, IAuthorizationContext authorizationContext) + : base(repository, authorizationContext) { } } diff --git a/Presentation.Web/Controllers/API/ItSystemTypeOptionController.cs b/Presentation.Web/Controllers/API/ItSystemTypeOptionController.cs index 59faf473fc..594291c9ea 100644 --- a/Presentation.Web/Controllers/API/ItSystemTypeOptionController.cs +++ b/Presentation.Web/Controllers/API/ItSystemTypeOptionController.cs @@ -1,13 +1,16 @@ -using Core.DomainModel.ItSystem; +using Core.ApplicationServices.Authorization; +using Core.DomainModel.ItSystem; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class ItSystemTypeOptionController : GenericOptionApiController { - public ItSystemTypeOptionController(IGenericRepository repository) - : base(repository) + public ItSystemTypeOptionController(IGenericRepository repository, IAuthorizationContext authorizationContext) + : base(repository, authorizationContext) { } } diff --git a/Presentation.Web/Controllers/API/ItSystemUsageController.cs b/Presentation.Web/Controllers/API/ItSystemUsageController.cs index 87d739f40b..393f686e33 100644 --- a/Presentation.Web/Controllers/API/ItSystemUsageController.cs +++ b/Presentation.Web/Controllers/API/ItSystemUsageController.cs @@ -1,31 +1,28 @@ using System; using System.Collections.Generic; -using System.Dynamic; -using System.IO; using System.Linq; using System.Net; using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; using System.Web.Http; -using AutoMapper.Internal; using Castle.Core.Internal; -using Core.ApplicationServices; +using Core.ApplicationServices.Authorization; using Core.DomainModel; -using Core.DomainModel.ItSystem; using Core.DomainModel.ItSystemUsage; using Core.DomainModel.Organization; using Core.DomainServices; +using Core.DomainServices.Authorization; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.API { + [PublicApi] public class ItSystemUsageController : GenericContextAwareApiController { private readonly IGenericRepository _orgUnitRepository; private readonly IGenericRepository _taskRepository; private readonly IItSystemUsageService _itSystemUsageService; - private readonly IGenericRepository _roleRepository; private readonly IGenericRepository _attachedOptionsRepository; @@ -33,28 +30,32 @@ public ItSystemUsageController(IGenericRepository repository, IGenericRepository orgUnitRepository, IGenericRepository taskRepository, IItSystemUsageService itSystemUsageService, - IGenericRepository roleRepository, - IGenericRepository attachedOptionsRepository) - : base(repository) + IGenericRepository attachedOptionsRepository, + IAuthorizationContext authorizationContext) + : base(repository, authorizationContext) { _orgUnitRepository = orgUnitRepository; _taskRepository = taskRepository; _itSystemUsageService = itSystemUsageService; - _roleRepository = roleRepository; _attachedOptionsRepository = attachedOptionsRepository; } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetSearchByOrganization(int organizationId, string q) { try { + //Local objects - must have full access to view + if (GetOrganizationReadAccessLevel(organizationId) != OrganizationDataReadAccessLevel.All) + { + return Forbidden(); + } var usages = Repository.Get( u => // filter by system usage name u.ItSystem.Name.Contains(q) && // system usage is only within the context - u.OrganizationId == organizationId - ); + u.OrganizationId == organizationId); return Ok(Map(usages)); } @@ -64,10 +65,16 @@ public HttpResponseMessage GetSearchByOrganization(int organizationId, string q) } } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetByOrganization(int organizationId, [FromUri] PagingModel pagingModel, [FromUri] string q, bool? overview) { try { + if (GetOrganizationReadAccessLevel(organizationId) != OrganizationDataReadAccessLevel.All) + { + return Forbidden(); + } + pagingModel.Where( u => // system usage is only within the context @@ -86,6 +93,7 @@ public HttpResponseMessage GetByOrganization(int organizationId, [FromUri] Pagin } } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO))] public override HttpResponseMessage GetSingle(int id) { @@ -93,12 +101,15 @@ public override HttpResponseMessage GetSingle(int id) { var item = Repository.GetByKey(id); - if (!AuthenticationService.HasReadAccess(KitosUser.Id, item)) + if (!AllowRead(item)) { - return Unauthorized(); + return Forbidden(); } - if (item == null) return NotFound(); + if (item == null) + { + return NotFound(); + } var dto = Map(item); @@ -115,82 +126,26 @@ public override HttpResponseMessage GetSingle(int id) } } - public HttpResponseMessage GetExcel(bool? csv, int organizationId) + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO))] + [SwaggerResponse(HttpStatusCode.Forbidden)] + [SwaggerResponse(HttpStatusCode.NotFound)] + public HttpResponseMessage GetByItSystemAndOrganization(int itSystemId, int organizationId) { try { - var usages = Repository.Get( - u => - // system usage is only within the context - u.OrganizationId == organizationId - ); + var usage = Repository.Get(u => u.ItSystemId == itSystemId && u.OrganizationId == organizationId).FirstOrDefault(); - //if (!string.IsNullOrEmpty(q)) pagingModel.Where(usage => usage.ItSystem.Name.Contains(q)); - //var usages = Page(Repository.AsQueryable(), pagingModel); - - // mapping to DTOs for easy lazy loading of needed properties - var dtos = Map(usages); - - var roles = _roleRepository.Get().ToList(); - - var list = new List(); - var header = new ExpandoObject() as System.Collections.Generic.IDictionary; - header.Add("Aktiv", "Aktiv"); - header.Add("IT System", "IT System"); - header.Add("OrgUnit", "Ansv. organisationsenhed"); - foreach (var role in roles) - header.Add(role.Name, role.Name); - header.Add("AppType", "Applikationtype"); - header.Add("BusiType", "Forretningstype"); - header.Add("Anvender", "Anvender"); - header.Add("Udstiller", "Udstiller"); - header.Add("Overblik", "Overblik"); - list.Add(header); - foreach (var usage in dtos) + if (usage == null) { - var obj = new ExpandoObject() as System.Collections.Generic.IDictionary; - obj.Add("Aktiv", usage.MainContractIsActive); - obj.Add("IT System", usage.ItSystem.Name); - obj.Add("OrgUnit", usage.ResponsibleOrgUnitName); - foreach (var role in roles) - { - var roleId = role.Id; - obj.Add(role.Name, - String.Join(",", usage.Rights.Where(x => x.RoleId == roleId).Select(x => x.User.FullName))); - } - obj.Add("AppType", usage.ItSystem.AppTypeOptionName); - obj.Add("BusiType", usage.ItSystem.BusinessTypeName); - obj.Add("Anvender", usage.ActiveInterfaceUseCount + "(" + usage.InterfaceUseCount + ")"); - obj.Add("Udstiller", usage.InterfaceExhibitCount); - obj.Add("Overblik", usage.OverviewItSystemName); - list.Add(obj); + return NotFound(); } - var s = list.ToCsv(); - var bytes = Encoding.Unicode.GetBytes(s); - var stream = new MemoryStream(); - stream.Write(bytes, 0, bytes.Length); - stream.Seek(0, SeekOrigin.Begin); - - var result = new HttpResponseMessage(HttpStatusCode.OK); - result.Content = new StreamContent(stream); - result.Content.Headers.ContentType = new MediaTypeHeaderValue("text/csv"); - result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileNameStar = "itsystemanvendelsesoversigt.csv", DispositionType = "ISO-8859-1" }; - return result; - } - catch (Exception e) - { - return LogError(e); - } - } - - public HttpResponseMessage GetByItSystemAndOrganization(int itSystemId, int organizationId) - { - try - { - var usage = Repository.Get(u => u.ItSystemId == itSystemId && u.OrganizationId == organizationId).FirstOrDefault(); + if (!AllowRead(usage)) + { + return Forbidden(); + } - return usage == null ? NotFound() : Ok(Map(usage)); + return Ok(Map(usage)); } catch (Exception e) { @@ -202,15 +157,18 @@ public override HttpResponseMessage Post(ItSystemUsageDTO dto) { try { - //check for isreadonly here since no object has been created to check on yet - //if (!KitosUser.IsReadOnly) return Unauthorized(); var itsystemUsage = AutoMapper.Mapper.Map(dto); - if (!HasWriteAccess(itsystemUsage, Int32.Parse(KitosUser.DefaultOrganizationId.ToString()))) return Unauthorized(); + if (!AllowCreate(itsystemUsage)) + { + return Forbidden(); + } if (Repository.Get(usage => usage.ItSystemId == dto.ItSystemId && usage.OrganizationId == dto.OrganizationId).Any()) + { return Conflict("Usage already exist"); + } var sysUsage = _itSystemUsageService.Add(itsystemUsage, KitosUser); sysUsage.DataLevel = dto.DataLevel; @@ -240,7 +198,10 @@ public HttpResponseMessage DeleteByItSystemId(int itSystemId, int organizationId try { var usage = Repository.Get(u => u.ItSystemId == itSystemId && u.OrganizationId == organizationId).FirstOrDefault(); - if (usage == null) return NotFound(); + if (usage == null) + { + return NotFound(); + } //This will make sure we check for permissions and such... return base.Delete(usage.Id, organizationId); @@ -257,12 +218,20 @@ public HttpResponseMessage PostOrganizationUnitsUsingThisSystem(int id, [FromUri try { var usage = Repository.GetByKey(id); - if (usage == null) return NotFound(); - if (!HasWriteAccess(usage, organizationId)) return Unauthorized(); + if (usage == null) + { + return NotFound(); + } + if (!AllowModify(usage)) + { + return Forbidden(); + } var orgUnit = _orgUnitRepository.GetByKey(organizationUnit); - if (orgUnit == null) return NotFound(); - + if (orgUnit == null) + { + return NotFound(); + } usage.UsedBy.Add(new ItSystemUsageOrgUnitUsage { ItSystemUsageId = id, OrganizationUnitId = organizationUnit }); @@ -284,15 +253,27 @@ public HttpResponseMessage DeleteOrganizationUnitsUsingThisSystem(int id, [FromU try { var usage = Repository.GetByKey(id); - if (usage == null) return NotFound(); + if (usage == null) + { + return NotFound(); + } - if (!HasWriteAccess(usage, organizationId)) return Unauthorized(); + if (!AllowModify(usage)) + { + return Forbidden(); + } var orgUnit = _orgUnitRepository.GetByKey(organizationUnit); - if (orgUnit == null) return NotFound(); + if (orgUnit == null) + { + return NotFound(); + } var entity = usage.UsedBy.SingleOrDefault(x => x.ItSystemUsageId == id && x.OrganizationUnitId == organizationUnit); - if (entity == null) return NotFound(); + if (entity == null) + { + return NotFound(); + } usage.UsedBy.Remove(entity); @@ -315,7 +296,10 @@ public HttpResponseMessage PostTasksUsedByThisSystem(int id, int organizationId, { var usage = Repository.GetByKey(id); if (usage == null) return NotFound(); - if (!HasWriteAccess(usage, organizationId)) return Unauthorized(); + if (!AllowModify(usage)) + { + return Forbidden(); + } List tasks; if (taskId.HasValue) @@ -368,8 +352,15 @@ public HttpResponseMessage DeleteTasksUsedByThisSystem(int id, int organizationI try { var usage = Repository.GetByKey(id); - if (usage == null) return NotFound(); - if (!HasWriteAccess(usage, organizationId)) return Unauthorized(); + if (usage == null) + { + return NotFound(); + } + + if (!AllowModify(usage)) + { + return Forbidden(); + } var optOut = false; List tasks; @@ -429,6 +420,7 @@ public HttpResponseMessage DeleteTasksUsedByThisSystem(int id, int organizationI /// Optional filtering on task group /// Paging model /// List of TaskRefSelectedDTO + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetTasks(int id, bool? tasks, bool onlySelected, int? taskGroup, [FromUri] PagingModel pagingModel) { try @@ -458,6 +450,7 @@ public HttpResponseMessage GetTasks(int id, bool? tasks, bool onlySelected, int? else pagingModel.Where(taskRef => taskRef.Children.Count == 0); + pagingModel.WithPostProcessingFilter(AllowRead); var theTasks = Page(taskQuery, pagingModel).ToList(); var dtos = theTasks.Select(task => new TaskRefSelectedDTO() @@ -481,18 +474,5 @@ protected override void DeleteQuery(ItSystemUsage entity) { _itSystemUsageService.Delete(entity.Id); } - - protected override bool HasWriteAccess(ItSystemUsage obj, User user, int organizationId) - { - //if readonly - if (user.IsReadOnly && !user.IsGlobalAdmin) - return false; - // local admin have write access if the obj is in context - if (obj.IsInContext(organizationId) && - user.OrganizationRights.Any(x => x.OrganizationId == organizationId && (x.Role == OrganizationRole.LocalAdmin || x.Role == OrganizationRole.SystemModuleAdmin))) - return true; - - return base.HasWriteAccess(obj, user, organizationId); - } } } diff --git a/Presentation.Web/Controllers/API/ItSystemUsageOrgUnitUsageController.cs b/Presentation.Web/Controllers/API/ItSystemUsageOrgUnitUsageController.cs index 2d1d9ddf8f..da8842790e 100644 --- a/Presentation.Web/Controllers/API/ItSystemUsageOrgUnitUsageController.cs +++ b/Presentation.Web/Controllers/API/ItSystemUsageOrgUnitUsageController.cs @@ -1,31 +1,42 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Net.Http; using AutoMapper; +using Core.ApplicationServices.Authorization; using Core.DomainModel.ItSystemUsage; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.API { + [PublicApi] public class ItSystemUsageOrgUnitUsageController : BaseApiController { private readonly IGenericRepository _responsibleOrgUnitRepository; private readonly IGenericRepository _systemUsageRepository; - public ItSystemUsageOrgUnitUsageController(IGenericRepository responsibleOrgUnitRepository, IGenericRepository systemUsageRepository) + public ItSystemUsageOrgUnitUsageController( + IGenericRepository responsibleOrgUnitRepository, + IGenericRepository systemUsageRepository, + IAuthorizationContext authorizationContext) + :base(authorizationContext) { _responsibleOrgUnitRepository = responsibleOrgUnitRepository; _systemUsageRepository = systemUsageRepository; } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetOrgUnitsBySystemUsage(int id) { try { var items = _responsibleOrgUnitRepository.Get(x => x.ItSystemUsageId == id); var orgUnits = items.Select(x => x.OrganizationUnit); + orgUnits = orgUnits.Where(AllowRead); var dtos = Mapper.Map>(orgUnits); return Ok(dtos); @@ -36,13 +47,24 @@ public HttpResponseMessage GetOrgUnitsBySystemUsage(int id) } } + + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO))] + [SwaggerResponse(HttpStatusCode.Forbidden)] public HttpResponseMessage GetResponsibleBySystemUsage(int id, bool? responsible) { try { var systemUsage = _systemUsageRepository.GetByKey(id); - if (systemUsage.ResponsibleUsage == null) return Ok(); // TODO should be NotFound but ui router resolve redirects to mainpage on 404 + if (systemUsage.ResponsibleUsage == null) + { + return Ok(); // TODO should be NotFound but ui router resolve redirects to mainpage on 404 + } + + if (!AllowRead(systemUsage)) + { + return Forbidden(); + } var organizationUnit = systemUsage.ResponsibleUsage.OrganizationUnit; var dtos = Mapper.Map(organizationUnit); @@ -58,7 +80,7 @@ public HttpResponseMessage PostSetResponsibleOrgUnit(int usageId, int orgUnitId, { try { - var entity = _responsibleOrgUnitRepository.GetByKey(new object[] {usageId, orgUnitId}); + var entity = _responsibleOrgUnitRepository.GetByKey(new object[] { usageId, orgUnitId }); var systemUsage = _systemUsageRepository.GetByKey(usageId); systemUsage.ResponsibleUsage = entity; diff --git a/Presentation.Web/Controllers/API/ItSystemUsageRightsController.cs b/Presentation.Web/Controllers/API/ItSystemUsageRightsController.cs index f2ca2c83f0..8cdc345505 100644 --- a/Presentation.Web/Controllers/API/ItSystemUsageRightsController.cs +++ b/Presentation.Web/Controllers/API/ItSystemUsageRightsController.cs @@ -1,23 +1,33 @@ using System; using System.Collections.Generic; +using System.Net; using System.Net.Http; +using Core.ApplicationServices.Authorization; using Core.DomainModel.ItSystem; using Core.DomainModel.ItSystemUsage; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.API { + [PublicApi] public class ItSystemUsageRightsController : GenericRightsController { - public ItSystemUsageRightsController(IGenericRepository rightRepository, IGenericRepository objectRepository) : base(rightRepository, objectRepository) - {} + public ItSystemUsageRightsController( + IGenericRepository rightRepository, + IGenericRepository objectRepository, + IAuthorizationContext authorizationContext) + : base(rightRepository, objectRepository, authorizationContext) + { } /// /// Returns all ITSystemRights for a specific user /// /// Id of the user /// List of rights + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetRightsForUser(int userId) { try diff --git a/Presentation.Web/Controllers/API/LocalAdminController.cs b/Presentation.Web/Controllers/API/LocalAdminController.cs index 546385b864..d76db8b67d 100644 --- a/Presentation.Web/Controllers/API/LocalAdminController.cs +++ b/Presentation.Web/Controllers/API/LocalAdminController.cs @@ -3,10 +3,12 @@ using Core.DomainModel; using Core.DomainModel.Organization; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [InternalApi] public class LocalAdminController : BaseApiController { private readonly IOrganizationRoleService _organizationRoleService; diff --git a/Presentation.Web/Controllers/API/MethodController.cs b/Presentation.Web/Controllers/API/MethodController.cs index b914904581..1263d84561 100644 --- a/Presentation.Web/Controllers/API/MethodController.cs +++ b/Presentation.Web/Controllers/API/MethodController.cs @@ -1,9 +1,11 @@ using Core.DomainModel.ItSystem; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class MethodController : GenericOptionApiController { public MethodController(IGenericRepository repository) diff --git a/Presentation.Web/Controllers/API/MilestoneController.cs b/Presentation.Web/Controllers/API/MilestoneController.cs index e5d11722cd..807893b298 100644 --- a/Presentation.Web/Controllers/API/MilestoneController.cs +++ b/Presentation.Web/Controllers/API/MilestoneController.cs @@ -1,9 +1,11 @@ using Core.DomainModel.ItProject; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class MilestoneController : GenericContextAwareApiController { public MilestoneController(IGenericRepository repository) diff --git a/Presentation.Web/Controllers/API/OptionExtendController.cs b/Presentation.Web/Controllers/API/OptionExtendController.cs index 3da4d5f0d5..20b56f8db2 100644 --- a/Presentation.Web/Controllers/API/OptionExtendController.cs +++ b/Presentation.Web/Controllers/API/OptionExtendController.cs @@ -1,9 +1,11 @@ using Core.DomainModel.ItContract; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class OptionExtendController : GenericOptionApiController { public OptionExtendController(IGenericRepository repository) diff --git a/Presentation.Web/Controllers/API/OrganizationController.cs b/Presentation.Web/Controllers/API/OrganizationController.cs index 09a2c793fc..9c48b06782 100644 --- a/Presentation.Web/Controllers/API/OrganizationController.cs +++ b/Presentation.Web/Controllers/API/OrganizationController.cs @@ -9,10 +9,12 @@ using Core.DomainModel.Organization; using Core.DomainServices; using Newtonsoft.Json.Linq; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [InternalApi] public class OrganizationController : GenericContextAwareApiController { private readonly IOrganizationService _organizationService; @@ -150,7 +152,7 @@ public override HttpResponseMessage Patch(int id, int organizationId, JObject ob if (obj.GetValue("typeId", StringComparison.InvariantCultureIgnoreCase) != null) { // only global admin is allowed to change the type of an organization - return Unauthorized(); + return Forbidden(); } } diff --git a/Presentation.Web/Controllers/API/OrganizationRightController.cs b/Presentation.Web/Controllers/API/OrganizationRightController.cs index 2055f5195d..7f92566829 100644 --- a/Presentation.Web/Controllers/API/OrganizationRightController.cs +++ b/Presentation.Web/Controllers/API/OrganizationRightController.cs @@ -2,20 +2,21 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; -using Core.DomainModel; using Core.DomainServices; using Presentation.Web.Models; using System.Web.Http; using Core.DomainModel.Organization; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.API { - public class OrganizationRightController : BaseApiController + [InternalApi] + public class OrganizationRightController : GenericApiController { private readonly IGenericRepository _rightRepository; private readonly IGenericRepository _objectRepository; - public OrganizationRightController(IGenericRepository rightRepository, IGenericRepository objectRepository) + public OrganizationRightController(IGenericRepository rightRepository, IGenericRepository objectRepository) : base (rightRepository) { _rightRepository = rightRepository; _objectRepository = objectRepository; @@ -25,7 +26,10 @@ public virtual HttpResponseMessage GetAllRights() { try { - if (!IsGlobalAdmin()) return Unauthorized(); + if (!IsGlobalAdmin()) + { + return Forbidden(); + } var theRights = _rightRepository.Get(); var dtos = Map, IEnumerable>(theRights); @@ -63,7 +67,10 @@ public virtual HttpResponseMessage GetRightsWithRoleName(string roleName, bool? { try { - if (!IsGlobalAdmin()) return Unauthorized(); + if (!IsGlobalAdmin()) + { + return Forbidden(); + } var role = (OrganizationRole)Enum.Parse(typeof(OrganizationRole), roleName, true); var theRights = _rightRepository.Get(x => x.Role == role); var dtos = Map, IEnumerable>(theRights); @@ -125,17 +132,34 @@ public HttpResponseMessage DeleteByOrganization(int orgId, int userId, bool? byO } } - public HttpResponseMessage PostRightByOrganizationRight(bool? rightByOrganizationRight, int organizationId, int userId, OrganizationRight right) + public HttpResponseMessage PostRightByOrganizationRight(bool? rightByOrganizationRight, int organizationId, OrganizationRightDTO dto) { try { - // if user has any role within the organization (or global admin) they should be able to add new adminrights - if (!KitosUser.IsGlobalAdmin) - if (!_rightRepository.Get(r => r.UserId == userId && r.OrganizationId == organizationId).Any()) - return Unauthorized(); + var right = AutoMapper.Mapper.Map(dto); + + // Only global admin can set other users as global admins + if(right.Role == OrganizationRole.GlobalAdmin) + { + if (!KitosUser.IsGlobalAdmin) + return Forbidden(); + } + + // Only local and global admins can make users local admins + if(right.Role == OrganizationRole.LocalAdmin) + { + if(!KitosUser.IsGlobalAdmin && !KitosUser.IsLocalAdmin) + return Forbidden(); + } right.OrganizationId = organizationId; right.ObjectOwner = KitosUser; + + if (!base.HasWriteAccess(right, KitosUser, organizationId)) + { + return Forbidden(); + } + right.LastChangedByUser = KitosUser; right.LastChanged = DateTime.UtcNow; @@ -180,42 +204,6 @@ public virtual HttpResponseMessage GetRights(int id) } } - /// - /// Post a new right to the object - /// - /// The id of the object - /// - /// DTO of right - /// - public HttpResponseMessage PostRight(int id, int organizationId, OrganizationRightDTO dto) - { - try - { - if (!HasWriteAccess(id, KitosUser, organizationId)) - return Unauthorized(); - - var right = AutoMapper.Mapper.Map(dto); - right.OrganizationId = id; - right.ObjectOwner = KitosUser; - right.LastChangedByUser = KitosUser; - right.LastChanged = DateTime.UtcNow; - - right = _rightRepository.Insert(right); - _rightRepository.Save(); - - //TODO: FIX navigation properties not loading properly!!! - right.User = UserRepository.GetByKey(right.UserId); - - var outputDTO = AutoMapper.Mapper.Map(right); - - return Created(outputDTO); - } - catch (Exception e) - { - return Error(e); - } - } - /// /// Delete a right from the object /// @@ -228,12 +216,32 @@ public HttpResponseMessage Delete(int id, [FromUri] int rId, [FromUri] int uId, { try { - if (!HasWriteAccess(id, KitosUser, organizationId)) - return Unauthorized(); - var right = _rightRepository.Get(r => r.OrganizationId == id && r.Role == (OrganizationRole)rId && r.UserId == uId).FirstOrDefault(); - if (right == null) return NotFound(); + if (right == null) + { + return NotFound(); + } + + // Only global admin can set other users as global admins + if (right.Role == OrganizationRole.GlobalAdmin) + { + if (!KitosUser.IsGlobalAdmin) + return Forbidden(); + } + + // Only local and global admins can make users local admins + if (right.Role == OrganizationRole.LocalAdmin) + { + if (!KitosUser.IsGlobalAdmin && !KitosUser.IsLocalAdmin) + return Forbidden(); + } + + if(!base.HasWriteAccess(right, KitosUser, organizationId)) + { + return Forbidden(); + } + _rightRepository.DeleteByKey(right.Id); _rightRepository.Save(); @@ -245,19 +253,5 @@ public HttpResponseMessage Delete(int id, [FromUri] int rId, [FromUri] int uId, return Error(e); } } - - private bool HasWriteAccess(int objectId, User user, int organizationId) - { - if (user.IsGlobalAdmin) - return true; - - var obj = _objectRepository.GetByKey(objectId); - // local admin have write access if the obj is in context - if (obj.IsInContext(organizationId) && - user.OrganizationRights.Any(x => x.OrganizationId == organizationId && x.Role == OrganizationRole.LocalAdmin)) - return true; - - return obj.HasUserWriteAccess(user); - } } } diff --git a/Presentation.Web/Controllers/API/OrganizationUnitController.cs b/Presentation.Web/Controllers/API/OrganizationUnitController.cs index e9f6f2bd0e..c18eb4e8e6 100644 --- a/Presentation.Web/Controllers/API/OrganizationUnitController.cs +++ b/Presentation.Web/Controllers/API/OrganizationUnitController.cs @@ -8,10 +8,12 @@ using Core.DomainModel.Organization; using Core.DomainServices; using Newtonsoft.Json.Linq; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [InternalApi] public class OrganizationUnitController : GenericHierarchyApiController { private readonly IOrgUnitService _orgUnitService; diff --git a/Presentation.Web/Controllers/API/OrganizationUnitRightsController.cs b/Presentation.Web/Controllers/API/OrganizationUnitRightsController.cs index b6b6639a31..3d68f4ef22 100644 --- a/Presentation.Web/Controllers/API/OrganizationUnitRightsController.cs +++ b/Presentation.Web/Controllers/API/OrganizationUnitRightsController.cs @@ -4,10 +4,12 @@ using System.Net.Http; using Core.DomainModel.Organization; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [InternalApi] public class OrganizationUnitRightController : GenericRightsController { private readonly IOrgUnitService _orgUnitService; diff --git a/Presentation.Web/Controllers/API/OrganizationUnitRoleController.cs b/Presentation.Web/Controllers/API/OrganizationUnitRoleController.cs index 18263ba5b3..589f695f91 100644 --- a/Presentation.Web/Controllers/API/OrganizationUnitRoleController.cs +++ b/Presentation.Web/Controllers/API/OrganizationUnitRoleController.cs @@ -1,9 +1,11 @@ using Core.DomainModel.Organization; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [InternalApi] public class OrganizationUnitRoleController : GenericOptionApiController { public OrganizationUnitRoleController(IGenericRepository repository) diff --git a/Presentation.Web/Controllers/API/PasswordResetRequestController.cs b/Presentation.Web/Controllers/API/PasswordResetRequestController.cs index 1035cf5ca8..956cd4c1ed 100644 --- a/Presentation.Web/Controllers/API/PasswordResetRequestController.cs +++ b/Presentation.Web/Controllers/API/PasswordResetRequestController.cs @@ -4,11 +4,14 @@ using System.Web.Http; using Core.DomainModel; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.API { [AllowAnonymous] + [PublicApi] public class PasswordResetRequestController : BaseApiController { private readonly IUserService _userService; @@ -37,6 +40,7 @@ public HttpResponseMessage Post([FromBody] UserDTO input) } // GET api/PasswordResetRequest + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO))] public HttpResponseMessage Get(string requestId) { try diff --git a/Presentation.Web/Controllers/API/PaymentFrequencyController.cs b/Presentation.Web/Controllers/API/PaymentFrequencyController.cs index 260c5042d4..d7dde915b2 100644 --- a/Presentation.Web/Controllers/API/PaymentFrequencyController.cs +++ b/Presentation.Web/Controllers/API/PaymentFrequencyController.cs @@ -1,9 +1,11 @@ using Core.DomainModel.ItContract; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class PaymentFrequencyController : GenericOptionApiController { public PaymentFrequencyController(IGenericRepository repository) diff --git a/Presentation.Web/Controllers/API/PaymentMilestoneController.cs b/Presentation.Web/Controllers/API/PaymentMilestoneController.cs index 709ca8015d..55c034f14f 100644 --- a/Presentation.Web/Controllers/API/PaymentMilestoneController.cs +++ b/Presentation.Web/Controllers/API/PaymentMilestoneController.cs @@ -1,11 +1,16 @@ -using System.Net.Http; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; using System.Web.Http; using Core.DomainModel.ItContract; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.API { + [PublicApi] public class PaymentMilestoneController : GenericContextAwareApiController { public PaymentMilestoneController(IGenericRepository repository) @@ -13,6 +18,7 @@ public PaymentMilestoneController(IGenericRepository repositor { } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetByContractId(int id, [FromUri] bool? contract) { var items = Repository.Get(x => x.ItContractId == id); diff --git a/Presentation.Web/Controllers/API/PaymentModelController.cs b/Presentation.Web/Controllers/API/PaymentModelController.cs index 238ba9fd32..706383a547 100644 --- a/Presentation.Web/Controllers/API/PaymentModelController.cs +++ b/Presentation.Web/Controllers/API/PaymentModelController.cs @@ -1,9 +1,11 @@ using Core.DomainModel.ItContract; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class PaymentModelController : GenericOptionApiController { public PaymentModelController(IGenericRepository repository) diff --git a/Presentation.Web/Controllers/API/PriceRegulationController.cs b/Presentation.Web/Controllers/API/PriceRegulationController.cs index a9dbdf8936..50f4de9fff 100644 --- a/Presentation.Web/Controllers/API/PriceRegulationController.cs +++ b/Presentation.Web/Controllers/API/PriceRegulationController.cs @@ -1,9 +1,11 @@ using Core.DomainModel.ItContract; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class PriceRegulationController : GenericOptionApiController { public PriceRegulationController(IGenericRepository repository) diff --git a/Presentation.Web/Controllers/API/ProcurementStrategyController.cs b/Presentation.Web/Controllers/API/ProcurementStrategyController.cs index 58992553b1..8438076910 100644 --- a/Presentation.Web/Controllers/API/ProcurementStrategyController.cs +++ b/Presentation.Web/Controllers/API/ProcurementStrategyController.cs @@ -1,9 +1,11 @@ using Core.DomainModel.ItContract; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class ProcurementStrategyController : GenericOptionApiController { public ProcurementStrategyController(IGenericRepository repository) diff --git a/Presentation.Web/Controllers/API/PurchaseFormController.cs b/Presentation.Web/Controllers/API/PurchaseFormController.cs index 06aba5c022..0296806671 100644 --- a/Presentation.Web/Controllers/API/PurchaseFormController.cs +++ b/Presentation.Web/Controllers/API/PurchaseFormController.cs @@ -1,9 +1,11 @@ using Core.DomainModel.ItContract; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class PurchaseFormController : GenericOptionApiController { public PurchaseFormController(IGenericRepository repository) diff --git a/Presentation.Web/Controllers/API/ReferenceController.cs b/Presentation.Web/Controllers/API/ReferenceController.cs index 615fb5cef0..a3de9c1657 100644 --- a/Presentation.Web/Controllers/API/ReferenceController.cs +++ b/Presentation.Web/Controllers/API/ReferenceController.cs @@ -1,17 +1,14 @@ using Core.DomainModel; using Core.DomainServices; using Presentation.Web.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.Mvc; using System.Net.Http; using Newtonsoft.Json.Linq; using Core.ApplicationServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.API { + [PublicApi] public class ReferenceController : GenericApiController { public readonly IFeatureChecker _featureChecker; @@ -24,36 +21,44 @@ public override HttpResponseMessage Patch(int id, int organizationId, JObject ob { var reference = Repository.GetByKey(id); if (!CanModifyReference(reference)) - return Unauthorized(); + { + return Forbidden(); + } var result = base.PatchQuery(reference, obj); + return Ok(Map(result)); + } private bool CanModifyReference(ExternalReference entity) { if (entity.ObjectOwnerId == KitosUser.Id) + { return true; + } - if(_featureChecker.CanExecute(KitosUser, Feature.CanModifyContracts) && entity.ItContract != null) + if (_featureChecker.CanExecute(KitosUser, Feature.CanModifyContracts) && entity.ItContract != null) + { return true; + } if (_featureChecker.CanExecute(KitosUser, Feature.CanModifyProjects) && entity.ItProject != null) + { return true; + } if (_featureChecker.CanExecute(KitosUser, Feature.CanModifySystems) && entity.ItSystem != null) + { return true; + } if (_featureChecker.CanExecute(KitosUser, Feature.CanModifySystems) && entity.ItSystemUsage != null) + { return true; + } return false; } - - /* public override HttpResponseMessage Post(ExternalReferenceDTO dto) - { - dto.ItProjectId - return base.Post(dto); - }*/ } } \ No newline at end of file diff --git a/Presentation.Web/Controllers/API/RiskController.cs b/Presentation.Web/Controllers/API/RiskController.cs index 425c89c817..67bebf4907 100644 --- a/Presentation.Web/Controllers/API/RiskController.cs +++ b/Presentation.Web/Controllers/API/RiskController.cs @@ -1,17 +1,23 @@ using System; +using System.Collections.Generic; +using System.Net; using System.Net.Http; using Core.DomainModel.ItProject; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.API { + [PublicApi] public class RiskController : GenericContextAwareApiController { public RiskController(IGenericRepository repository) : base(repository) { } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetByProject(bool? getByProject, int projectId) { try diff --git a/Presentation.Web/Controllers/API/SSOConfigController.cs b/Presentation.Web/Controllers/API/SSOConfigController.cs index fdd160a0f2..a640266215 100644 --- a/Presentation.Web/Controllers/API/SSOConfigController.cs +++ b/Presentation.Web/Controllers/API/SSOConfigController.cs @@ -1,21 +1,23 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Net; using System.Net.Http; -using System.Web; using System.Web.Http; +using Presentation.Web.Infrastructure.Attributes; +using Presentation.Web.Models; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.API { + [PublicApi] public class SSOConfigController : BaseApiController { [AllowAnonymous] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO))] public HttpResponseMessage Get() { var SSOGateway = System.Web.Configuration.WebConfigurationManager.AppSettings["SSOGateway"]; var SSOAudience = System.Web.Configuration.WebConfigurationManager.AppSettings["SSOAudience"]; - var ssoConfig = new { SSOGateway = SSOGateway, SSOAudience = SSOAudience }; + var ssoConfig = new SSOConfigDTO{ SSOGateway = SSOGateway, SSOAudience = SSOAudience }; return Ok(ssoConfig); } } diff --git a/Presentation.Web/Controllers/API/SensitiveDataTypeController.cs b/Presentation.Web/Controllers/API/SensitiveDataTypeController.cs index 5ad1c7109b..54763a6434 100644 --- a/Presentation.Web/Controllers/API/SensitiveDataTypeController.cs +++ b/Presentation.Web/Controllers/API/SensitiveDataTypeController.cs @@ -1,10 +1,12 @@ using Core.DomainModel.ItSystem; using Core.DomainModel.ItSystemUsage; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class SensitiveDataTypeController : GenericOptionApiController { public SensitiveDataTypeController(IGenericRepository repository) diff --git a/Presentation.Web/Controllers/API/StakeholderController.cs b/Presentation.Web/Controllers/API/StakeholderController.cs index 4399682a57..e969fa4d16 100644 --- a/Presentation.Web/Controllers/API/StakeholderController.cs +++ b/Presentation.Web/Controllers/API/StakeholderController.cs @@ -1,9 +1,11 @@ using Core.DomainModel.ItProject; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class StakeholderController : GenericContextAwareApiController { public StakeholderController(IGenericRepository repository) : base(repository) diff --git a/Presentation.Web/Controllers/API/TaskRefController.cs b/Presentation.Web/Controllers/API/TaskRefController.cs index ee7c0c48f8..97b6ed7454 100644 --- a/Presentation.Web/Controllers/API/TaskRefController.cs +++ b/Presentation.Web/Controllers/API/TaskRefController.cs @@ -1,12 +1,16 @@ -using System.Net.Http; +using System.Net; +using System.Net.Http; using System.Web.Http; using Core.DomainModel; using Core.DomainModel.Organization; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.API { + [PublicApi] public class TaskRefController : GenericHierarchyApiController { public TaskRefController(IGenericRepository repository) @@ -14,6 +18,7 @@ public TaskRefController(IGenericRepository repository) { } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO))] public HttpResponseMessage GetRootsByOrgUnit(int orgUnitId, bool? roots, [FromUri] PagingModel paging) { paging.Where(taskRef => taskRef.OwnedByOrganizationUnitId == orgUnitId || taskRef.AccessModifier == AccessModifier.Public); @@ -21,6 +26,7 @@ public HttpResponseMessage GetRootsByOrgUnit(int orgUnitId, bool? roots, [FromUr return base.GetRoots(true, paging); } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO))] public HttpResponseMessage GetChildrenByOrgUnit(int id, int orgUnitId, bool? children, [FromUri] PagingModel paging) { paging.Where(taskRef => taskRef.OwnedByOrganizationUnitId == orgUnitId || taskRef.AccessModifier == AccessModifier.Public); @@ -28,6 +34,7 @@ public HttpResponseMessage GetChildrenByOrgUnit(int id, int orgUnitId, bool? chi return base.GetChildren(id, true, paging); } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO))] public HttpResponseMessage GetRootsByOrg(int orgId, bool? roots, [FromUri] PagingModel paging) { paging.Where(taskRef => taskRef.OwnedByOrganizationUnit.OrganizationId == orgId || taskRef.AccessModifier == AccessModifier.Public); @@ -35,6 +42,7 @@ public HttpResponseMessage GetRootsByOrg(int orgId, bool? roots, [FromUri] Pagin return base.GetRoots(true, paging); } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO))] public HttpResponseMessage GetChildrenByOrg(int id, int orgId, bool? children, [FromUri] PagingModel paging) { paging.Where(taskRef => taskRef.OwnedByOrganizationUnit.OrganizationId == orgId || taskRef.AccessModifier == AccessModifier.Public); diff --git a/Presentation.Web/Controllers/API/TaskUsageController.cs b/Presentation.Web/Controllers/API/TaskUsageController.cs index 986c71893c..9654d0a27e 100644 --- a/Presentation.Web/Controllers/API/TaskUsageController.cs +++ b/Presentation.Web/Controllers/API/TaskUsageController.cs @@ -15,10 +15,13 @@ using Core.DomainModel.Organization; using Core.DomainServices; using Newtonsoft.Json.Linq; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.API { + [PublicApi] public class TaskUsageController : GenericHierarchyApiController { private readonly IGenericRepository _orgUnitRepository; @@ -31,11 +34,17 @@ public TaskUsageController(IGenericRepository repository, IGenericRep _taskRepository = taskRepository; } + [HttpGet] + [Route("api/taskUsage/")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage Get(int orgUnitId, int organizationId, [FromUri] PagingModel pagingModel) { return Get(orgUnitId, organizationId, false, pagingModel); } + [HttpGet] + [Route("api/taskUsage/")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage Get(int orgUnitId, int organizationId, bool onlyStarred, [FromUri] PagingModel pagingModel) { try @@ -65,6 +74,8 @@ public HttpResponseMessage Get(int orgUnitId, int organizationId, bool onlyStarr } } + [HttpPost] + [Route("api/taskUsage/taskGroup")] public HttpResponseMessage PostTaskGroup(int orgUnitId, int? taskId) { try @@ -116,6 +127,36 @@ public HttpResponseMessage PostTaskGroup(int orgUnitId, int? taskId) } } + [HttpPost] + [Route("api/taskUsage/")] + public HttpResponseMessage Post(TaskUsageDTO taskUsageDto) + { + try + { + var item = Map(taskUsageDto); + item.ObjectOwner = KitosUser; + item.LastChangedByUser = KitosUser; + + var savedItem = PostQuery(item); + + return Created(Map(savedItem), new Uri(Request.RequestUri + "/" + savedItem.Id)); + } + catch (Exception e) + { + // check if inner message is a duplicate, if so return conflict + if (e.InnerException?.InnerException != null) + { + if (e.InnerException.InnerException.Message.Contains("Duplicate entry")) + { + return Conflict(e.InnerException.InnerException.Message); + } + } + return LogError(e); + } + } + + [HttpDelete] + [Route("api/taskUsage/")] public HttpResponseMessage DeleteTaskGroup(int orgUnitId, int? taskId) { try @@ -152,6 +193,8 @@ public HttpResponseMessage DeleteTaskGroup(int orgUnitId, int? taskId) } } + [HttpGet] + [Route("api/taskUsage/")] public HttpResponseMessage GetExcel(bool? csv, int orgUnitId, bool onlyStarred) { try diff --git a/Presentation.Web/Controllers/API/TerminationDeadlineController.cs b/Presentation.Web/Controllers/API/TerminationDeadlineController.cs index cabd0c41e9..1dd0d19ca0 100644 --- a/Presentation.Web/Controllers/API/TerminationDeadlineController.cs +++ b/Presentation.Web/Controllers/API/TerminationDeadlineController.cs @@ -1,9 +1,11 @@ using Core.DomainModel.ItContract; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class TerminationDeadlineController : GenericOptionApiController { public TerminationDeadlineController(IGenericRepository repository) diff --git a/Presentation.Web/Controllers/API/TextController.cs b/Presentation.Web/Controllers/API/TextController.cs index 8cd7a681e7..b0a88fb74c 100644 --- a/Presentation.Web/Controllers/API/TextController.cs +++ b/Presentation.Web/Controllers/API/TextController.cs @@ -1,18 +1,18 @@ using System; using System.Collections.Generic; -using System.Linq; +using System.Net; using System.Net.Http; -using System.Security; using System.Web.Http; using Core.DomainModel; using Core.DomainServices; -using Newtonsoft.Json.Linq; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; -using Presentation.Web.Models.Exceptions; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.API { [AllowAnonymous] + [PublicApi] public class TextController : GenericApiController { protected readonly IGenericRepository _repository; @@ -23,6 +23,7 @@ public TextController(IGenericRepository repository) _repository = repository; } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public override HttpResponseMessage GetAll([FromUri] PagingModel paging) { try diff --git a/Presentation.Web/Controllers/API/TsaController.cs b/Presentation.Web/Controllers/API/TsaController.cs index db202ff5eb..9fba96ed48 100644 --- a/Presentation.Web/Controllers/API/TsaController.cs +++ b/Presentation.Web/Controllers/API/TsaController.cs @@ -1,9 +1,11 @@ using Core.DomainModel.ItSystem; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [PublicApi] public class TsaController : GenericOptionApiController { public TsaController(IGenericRepository repository) : base(repository) diff --git a/Presentation.Web/Controllers/API/UploadFileController.cs b/Presentation.Web/Controllers/API/UploadFileController.cs index d1820c6304..574644aca7 100644 --- a/Presentation.Web/Controllers/API/UploadFileController.cs +++ b/Presentation.Web/Controllers/API/UploadFileController.cs @@ -1,13 +1,11 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Net.Http; using System.Web; -using System.Web.Mvc; -using System.IO; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.API { + [InternalApi] public class UploadFileController : BaseApiController { public HttpResponseMessage Post() diff --git a/Presentation.Web/Controllers/API/UsageDataworkerController.cs b/Presentation.Web/Controllers/API/UsageDataworkerController.cs index 7fe8e9e639..3e6ec830b8 100644 --- a/Presentation.Web/Controllers/API/UsageDataworkerController.cs +++ b/Presentation.Web/Controllers/API/UsageDataworkerController.cs @@ -1,14 +1,10 @@ using Core.DomainModel.ItSystem; using Core.DomainServices; -using Presentation.Web.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.Mvc; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.API { + [PublicApi] public class UsageDataworkerController : GenericApiController { public UsageDataworkerController(IGenericRepository repository) diff --git a/Presentation.Web/Controllers/API/UserController.cs b/Presentation.Web/Controllers/API/UserController.cs index 32452a5989..009af7e71b 100644 --- a/Presentation.Web/Controllers/API/UserController.cs +++ b/Presentation.Web/Controllers/API/UserController.cs @@ -1,41 +1,29 @@ using System; using System.Collections.Generic; -using System.Dynamic; -using System.IO; using System.Linq; -using System.Net; using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; using System.Web.Http; using AutoMapper; -using Core.ApplicationServices; using Core.DomainModel; -using Core.DomainModel.ItContract; -using Core.DomainModel.ItProject; -using Core.DomainModel.ItSystem; using Core.DomainModel.Organization; using Core.DomainServices; using Newtonsoft.Json.Linq; -using Ninject; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; namespace Presentation.Web.Controllers.API { + [InternalApi] public class UserController : GenericApiController { private readonly IUserService _userService; private readonly IOrganizationService _organizationService; - private readonly IKernel _kernel; - public UserController(IGenericRepository repository, IUserService userService, IOrganizationService organizationService, IKernel kernel) + public UserController(IGenericRepository repository, IUserService userService, IOrganizationService organizationService) : base(repository) { _userService = userService; _organizationService = organizationService; - - // TODO: this is bad crosscutting of concerns. refactor / extract into separate controller - _kernel = kernel; // we need this for retrieving userroles when creating a csv file. } public override HttpResponseMessage Post(UserDTO dto) @@ -142,24 +130,6 @@ public override HttpResponseMessage Patch(int id, int organizationId, JObject ob return base.Patch(id, organizationId, obj); } - //public HttpResponseMessage PostTokenRequest(bool? token, int userId) - //{ - // try - // { - // var user = Repository.GetByKey(userId); - // if (user == null) - // return NotFound(); - - // user.UniqueId = Guid.NewGuid(); - // PatchQuery(user, null); - // return Ok(user.Uuid); - // } - // catch (Exception e) - // { - // return LogError(e); - // } - //} - public HttpResponseMessage GetBySearch(string q) { try @@ -226,125 +196,6 @@ public HttpResponseMessage GetOverview(bool? overview, int orgId, [FromUri] Pagi } } - public HttpResponseMessage GetExcel([FromUri] bool? csv, [FromUri] int orgId) - { - try - { - var users = Repository.Get(u => u.OrganizationRights.Count(r => r.OrganizationId == orgId) != 0); - - var dtos = Map(users); - - var list = new List(); - var header = new ExpandoObject() as IDictionary; - header.Add("Fornavn", "Fornavn"); - header.Add("Efternavn", "Efternavn"); - header.Add("Email", "Email"); - header.Add("DefaultUserStartPreference", "DefaultUserStartPreference"); - header.Add("Organisationsenhed", "Default org.enhed"); - header.Add("Advis", "Advis"); - header.Add("Oprettet", "Oprettet Af"); - header.Add("OrgRoller", "Organisations roller"); - header.Add("ITProjektRoller", "ITProjekt roller"); - header.Add("ITSystemRoller", "ITSystem roller"); - header.Add("ITKontraktRoller", "ITKontrakt roller"); - list.Add(header); - - foreach (var user in dtos) - { - var obj = new ExpandoObject() as IDictionary; - obj.Add("Fornavn", user.Name); - obj.Add("Efternavn", user.LastName); - obj.Add("Email", user.Email); - obj.Add("DefaultUserStartPreference", user.DefaultUserStartPreference); - obj.Add("Organisationsenhed", user.DefaultOrganizationUnitName); - obj.Add("Advis", user.LastAdvisDate.HasValue ? user.LastAdvisDate.Value.ToString("dd-MM-yy") : "Ikke sendt"); - obj.Add("Oprettet", user.ObjectOwnerName + " " + user.ObjectOwnerLastName); - obj.Add("OrgRoller", GetOrgRights(orgId, user.Id)); - obj.Add("ITProjektRoller", GetProjectRights(user.Id)); - obj.Add("ITSystemRoller", GetSystemRights(user.Id)); - obj.Add("ITKontraktRoller", GetContractRights(user.Id)); - list.Add(obj); - } - - var csvList = list.ToCsv(); - var bytes = Encoding.Unicode.GetBytes(csvList); - var stream = new MemoryStream(); - stream.Write(bytes, 0, bytes.Length); - stream.Seek(0, SeekOrigin.Begin); - - var result = new HttpResponseMessage(HttpStatusCode.OK); - result.Content = new StreamContent(stream); - result.Content.Headers.ContentType = new MediaTypeHeaderValue("text/csv"); - result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileNameStar = "brugerkatalog.csv", DispositionType = "ISO-8859-1" }; - return result; - } - catch (Exception e) - { - return LogError(e); - } - } - - #region GetRights - - private string GetOrgRights(int orgId, int userId) - { - var rightsRepository = _kernel.Get>(); - var orgUnitService = _kernel.Get(); - - var orgUnits = orgUnitService.GetSubTree(orgId); - - var theRights = new List(); - foreach (var orgUnit in orgUnits) - { - var id = orgUnit.Id; - theRights.AddRange(rightsRepository.Get(r => r.ObjectId == id && r.UserId == userId)); - } - var dtos = Mapper.Map>(theRights); - - return StringifyRights(dtos); - } - - private string GetProjectRights(int userId) - { - var rightsRepository = _kernel.Get>(); - - var theRights = rightsRepository.Get(r => r.UserId == userId); - - return StringifyRights(Mapper.Map>(theRights)); - } - - private string GetSystemRights(int userId) - { - var rightsRepository = _kernel.Get>(); - - var theRights = rightsRepository.Get(r => r.UserId == userId); - - return StringifyRights(Mapper.Map>(theRights)); - } - - private string GetContractRights(int userId) - { - var rightsRepository = _kernel.Get>(); - - var theRights = rightsRepository.Get(r => r.UserId == userId); - - return StringifyRights(Mapper.Map>(theRights)); - } - - private static string StringifyRights(List dtos) - { - var builder = new StringBuilder(); - foreach (var dto in dtos) - { - builder.Append(dto.ObjectName).Append(':').Append(dto.RoleName); - if (dtos.Last() != dto) - builder.Append(','); - } - return builder.ToString(); - } - - #endregion - public HttpResponseMessage GetNameIsAvailable(string checkname, int orgId) { try @@ -402,11 +253,23 @@ protected override bool HasWriteAccess(User obj, User user, int organizationId) if (user.IsReadOnly && !user.IsGlobalAdmin) return false; - var isLocalAdmin = KitosUser.OrganizationRights.Any(x => x.OrganizationId == organizationId && x.Role == OrganizationRole.LocalAdmin); - if (isLocalAdmin) - return true; - return base.HasWriteAccess(obj, user, organizationId); } + + /// + /// Deletes user from the system + /// + /// The id of the user to be deleted + /// Not used in this case. Should remain empty + /// + public override HttpResponseMessage Delete(int id, int organizationId = 0) + { + if (!KitosUser.OrganizationRights.Any(x => x.Role == OrganizationRole.GlobalAdmin || x.Role == OrganizationRole.LocalAdmin || x.Role == OrganizationRole.OrganizationModuleAdmin)) + { + return Forbidden(); + } + + return base.Delete(id, organizationId); + } } } diff --git a/Presentation.Web/Controllers/API/WishController.cs b/Presentation.Web/Controllers/API/WishController.cs index 2aee72e448..eb2dd6bffd 100644 --- a/Presentation.Web/Controllers/API/WishController.cs +++ b/Presentation.Web/Controllers/API/WishController.cs @@ -1,11 +1,16 @@ -using System.Net.Http; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; using System.Web.Http; using Core.DomainModel.ItSystem; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.API { + [PublicApi] public class WishController : GenericContextAwareApiController { public WishController(IGenericRepository repository) @@ -13,6 +18,7 @@ public WishController(IGenericRepository repository) { } + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiReturnDTO>))] public HttpResponseMessage GetWishes([FromUri] int userId, [FromUri] int usageId) { var wishes = Repository.Get(x => x.ItSystemUsageId == usageId && (x.IsPublic || x.UserId == userId)); diff --git a/Presentation.Web/Controllers/OData/AccessTypesController.cs b/Presentation.Web/Controllers/OData/AccessTypesController.cs index 155e8226f9..818bc5a251 100644 --- a/Presentation.Web/Controllers/OData/AccessTypesController.cs +++ b/Presentation.Web/Controllers/OData/AccessTypesController.cs @@ -1,12 +1,14 @@ using Core.ApplicationServices; using Core.DomainServices; -using Core.DomainModel; using Core.DomainModel.ItSystem; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData { + [PublicApi] public class AccessTypesController : BaseEntityController { + public AccessTypesController(IGenericRepository repository, IAuthenticationService authService) : base(repository, authService) { diff --git a/Presentation.Web/Controllers/OData/AdviceController.cs b/Presentation.Web/Controllers/OData/AdviceController.cs index 80cdf51db1..c06f28578a 100644 --- a/Presentation.Web/Controllers/OData/AdviceController.cs +++ b/Presentation.Web/Controllers/OData/AdviceController.cs @@ -4,30 +4,31 @@ using Core.DomainServices; using Hangfire; using System; +using System.Collections.Generic; using System.Linq; using System.Web.Http; -using System.Web.Http.Results; using System.Web.OData; using System.Web.OData.Results; -using System.Web.OData.Routing; +using Presentation.Web.Infrastructure.Attributes; +using Presentation.Web.Models; +using Swashbuckle.OData; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.OData { using Core.DomainModel.AdviceSent; using System.Net; + [PublicApi] public class AdviceController : BaseEntityController { - - IAuthenticationService _authService; - IAdviceService _adviceService; - IGenericRepository _repository; - IGenericRepository _sentRepository; + readonly IAdviceService _adviceService; + readonly IGenericRepository _repository; + readonly IGenericRepository _sentRepository; public AdviceController(IAdviceService adviceService, IGenericRepository repository, IAuthenticationService authService, IGenericRepository sentRepository) : base(repository, authService) { - _authService = authService; _adviceService = adviceService; _repository = repository; _sentRepository = sentRepository; @@ -36,7 +37,6 @@ public AdviceController(IAdviceService adviceService, IGenericRepository [EnableQuery] public override IHttpActionResult Post(Advice advice) { - var response = base.Post(advice); if (response.GetType() == typeof(CreatedODataResult)) { @@ -62,14 +62,14 @@ public override IHttpActionResult Post(Advice advice) switch (advice.Scheduling) { case Scheduling.Immediate: var jobId = BackgroundJob.Enqueue( - () => _adviceService.sendAdvice(createdRepsonse.Entity.Id)); + () => _adviceService.SendAdvice(createdRepsonse.Entity.Id)); break; case Scheduling.Hour: string cron = "0 * * * *"; RecurringJob.AddOrUpdate(name, - () => _adviceService.sendAdvice(createdRepsonse.Entity.Id), + () => _adviceService.SendAdvice(createdRepsonse.Entity.Id), cron); break; case Scheduling.Day: @@ -79,7 +79,7 @@ public override IHttpActionResult Post(Advice advice) cron = "0 8 * * *"; RecurringJob.AddOrUpdate(name, - () => _adviceService.sendAdvice(createdRepsonse.Entity.Id), + () => _adviceService.SendAdvice(createdRepsonse.Entity.Id), cron); break; case Scheduling.Week: @@ -87,7 +87,7 @@ public override IHttpActionResult Post(Advice advice) cron = "0 8 * * " + weekDay; RecurringJob.AddOrUpdate(name, - () => _adviceService.sendAdvice(createdRepsonse.Entity.Id), + () => _adviceService.SendAdvice(createdRepsonse.Entity.Id), cron); break; case Scheduling.Month: @@ -96,7 +96,7 @@ public override IHttpActionResult Post(Advice advice) cron = "0 8 " + day + " * *"; RecurringJob.AddOrUpdate(name, - () => _adviceService.sendAdvice(createdRepsonse.Entity.Id), + () => _adviceService.SendAdvice(createdRepsonse.Entity.Id), cron); break; case Scheduling.Year: @@ -106,7 +106,7 @@ public override IHttpActionResult Post(Advice advice) cron = "0 8 " + day + " " + month + " *"; RecurringJob.AddOrUpdate(name, - () => _adviceService.sendAdvice(createdRepsonse.Entity.Id), + () => _adviceService.SendAdvice(createdRepsonse.Entity.Id), cron); break; } @@ -129,7 +129,7 @@ public override IHttpActionResult Patch(int key, Delta delta) { try { - var advice = delta.GetEntity(); + var advice = delta.GetInstance(); switch (advice.Scheduling) { @@ -138,7 +138,7 @@ public override IHttpActionResult Patch(int key, Delta delta) string cron = "0 * * * *"; RecurringJob.AddOrUpdate(advice.JobId, - () => _adviceService.sendAdvice(key), + () => _adviceService.SendAdvice(key), cron); break; case Scheduling.Day: @@ -146,7 +146,7 @@ public override IHttpActionResult Patch(int key, Delta delta) cron = "0 8 * * *"; RecurringJob.AddOrUpdate(advice.JobId, - () => _adviceService.sendAdvice(key), + () => _adviceService.SendAdvice(key), cron); break; case Scheduling.Week: @@ -154,7 +154,7 @@ public override IHttpActionResult Patch(int key, Delta delta) cron = "0 8 * * " + weekDay; RecurringJob.AddOrUpdate(advice.JobId, - () => _adviceService.sendAdvice(key), + () => _adviceService.SendAdvice(key), cron); break; case Scheduling.Month: @@ -163,7 +163,7 @@ public override IHttpActionResult Patch(int key, Delta delta) cron = "0 8 " + day + " * *"; RecurringJob.AddOrUpdate(advice.JobId, - () => _adviceService.sendAdvice(key), + () => _adviceService.SendAdvice(key), cron); break; case Scheduling.Year: @@ -173,7 +173,7 @@ public override IHttpActionResult Patch(int key, Delta delta) cron = "0 8 " + day + " " + month + " *"; RecurringJob.AddOrUpdate(advice.JobId, - () => _adviceService.sendAdvice(key), + () => _adviceService.SendAdvice(key), cron); break; } @@ -189,31 +189,28 @@ public override IHttpActionResult Patch(int key, Delta delta) } [EnableQuery] - [ODataRoute("GetAdvicesByObjectID(id={id},type={type})")] - public IHttpActionResult GetByObjectID(int id,ObjectType type) + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse>))] + public IHttpActionResult GetAdvicesByObjectID(int id, ObjectType type) { - if (UserId == 0) - return Unauthorized(); - var hasOrg = typeof(IHasOrganization).IsAssignableFrom(typeof(Advice)); - if (_authService.HasReadAccessOutsideContext(UserId) || hasOrg == false) + if (AuthService.HasReadAccessOutsideContext(UserId) || hasOrg == false) return Ok(Repository.AsQueryable().Where(x=> x.RelationId == id && x.Type == type)); return Ok(Repository.AsQueryable() - .Where(x => ((IHasOrganization)x).OrganizationId == _authService.GetCurrentOrganizationId(UserId) && x.RelationId == id && x.Type == type)); + .Where(x => ((IHasOrganization)x).OrganizationId == AuthService.GetCurrentOrganizationId(UserId) && x.RelationId == id && x.Type == type)); } [EnableQuery] - [ODataRoute("Organizations({orgKey})/Advice")] - public IHttpActionResult GetByOrganization(int orgKey) + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse>))] + [SwaggerResponse(HttpStatusCode.Forbidden)] + public IHttpActionResult GetByOrganization([FromODataUri]int orgKey) { - if (UserId == 0) - return Unauthorized(); - - var currentOrgId = _authService.GetCurrentOrganizationId(UserId); + var currentOrgId = AuthService.GetCurrentOrganizationId(UserId); if (orgKey != currentOrgId) - return Unauthorized(); + { + return Forbidden(); + } var result = _adviceService.GetAdvicesForOrg(orgKey); @@ -225,17 +222,20 @@ public override IHttpActionResult Delete(int key) { var entity = Repository.AsQueryable().SingleOrDefault(m => m.Id == key); if (entity == null) + { return NotFound(); + } var anySents = _sentRepository.AsQueryable().Any(m => m.AdviceId == key); if (anySents) { - return StatusCode(HttpStatusCode.Forbidden); + return Forbidden(); } - if (!_authService.HasWriteAccess(UserId, entity)) - return StatusCode(HttpStatusCode.Forbidden); - + if (!AuthService.HasWriteAccess(UserId, entity)) + { + return Forbidden(); + } try { diff --git a/Presentation.Web/Controllers/OData/AdviceSentController.cs b/Presentation.Web/Controllers/OData/AdviceSentController.cs index 5988331b8d..7681a74d04 100644 --- a/Presentation.Web/Controllers/OData/AdviceSentController.cs +++ b/Presentation.Web/Controllers/OData/AdviceSentController.cs @@ -1,15 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.AdviceSent; using Core.DomainServices; -using Presentation.Web.Controllers.OData; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.Mvc; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData { + [PublicApi] public class AdviceSentController : BaseEntityController { public AdviceSentController(IGenericRepository repository, IAuthenticationService authService): diff --git a/Presentation.Web/Controllers/OData/ArchivePeriodsController.cs b/Presentation.Web/Controllers/OData/ArchivePeriodsController.cs index e9bdd5d638..c2e3c5c4eb 100644 --- a/Presentation.Web/Controllers/OData/ArchivePeriodsController.cs +++ b/Presentation.Web/Controllers/OData/ArchivePeriodsController.cs @@ -1,14 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.Mvc; -using Core.ApplicationServices; +using Core.ApplicationServices; using Core.DomainModel.ItSystem; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData { + [PublicApi] public class ArchivePeriodsController : BaseEntityController { // GET: ArchivePeriode diff --git a/Presentation.Web/Controllers/OData/AttachedOptions/AttachedOptionsController.cs b/Presentation.Web/Controllers/OData/AttachedOptions/AttachedOptionsController.cs index 830afefdbd..1be4556687 100644 --- a/Presentation.Web/Controllers/OData/AttachedOptions/AttachedOptionsController.cs +++ b/Presentation.Web/Controllers/OData/AttachedOptions/AttachedOptionsController.cs @@ -1,19 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel; -using Core.DomainModel.ItSystem; using Core.DomainServices; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Web; -using System.Web.Http; -using System.Web.Mvc; -using System.Web.OData; -using System.Web.OData.Routing; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.AttachedOptions { + [PublicApi] public class AttachedOptionsController : BaseEntityController { public AttachedOptionsController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/AttachedOptions/AttachedOptionsFunctionController.cs b/Presentation.Web/Controllers/OData/AttachedOptions/AttachedOptionsFunctionController.cs index 6499d7f70f..ddd4646fa9 100644 --- a/Presentation.Web/Controllers/OData/AttachedOptions/AttachedOptionsFunctionController.cs +++ b/Presentation.Web/Controllers/OData/AttachedOptions/AttachedOptionsFunctionController.cs @@ -1,50 +1,47 @@ using Core.ApplicationServices; using Core.DomainModel; -using Core.DomainModel.ItSystem; using Core.DomainServices; using System; using System.Collections.Generic; using System.Linq; using System.Net; -using System.Web; using System.Web.Http; -using System.Web.Mvc; using System.Web.OData; using System.Web.OData.Routing; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.AttachedOptions { + [InternalApi] public class AttachedOptionsFunctionController : AttachedOptionsController where TEntity : Entity where TOption : OptionHasChecked where TLocalOption : LocalOptionEntity { - IGenericRepository _AttachedOptionRepository; - IAuthenticationService _authService; - IGenericRepository _OptionRepository; - IGenericRepository _LocalOptionRepository; - - public AttachedOptionsFunctionController(IGenericRepository repository, IAuthenticationService authService, - IGenericRepository OptionRepository, - IGenericRepository LocalOptionRepository) + private readonly IGenericRepository _attachedOptionRepository; + private readonly IGenericRepository _optionRepository; + private readonly IGenericRepository _localOptionRepository; + + public AttachedOptionsFunctionController( + IGenericRepository repository, + IAuthenticationService authService, + IGenericRepository optionRepository, + IGenericRepository localOptionRepository) : base(repository, authService) { - _authService = authService; - _AttachedOptionRepository = repository; - _OptionRepository = OptionRepository; - _LocalOptionRepository = LocalOptionRepository; + _attachedOptionRepository = repository; + _optionRepository = optionRepository; + _localOptionRepository = localOptionRepository; } public virtual IHttpActionResult GetOptionsByObjectIDAndType(int id, EntityType entitytype, OptionType optiontype) { - if (UserId == 0) - return Unauthorized(); - var orgId = _authService.GetCurrentOrganizationId(UserId); + var orgId = AuthService.GetCurrentOrganizationId(UserId); - var globalOptionData = _OptionRepository.AsQueryable().Where(s => s.IsEnabled); - var localpersonalData = _LocalOptionRepository.AsQueryable().Where(p => p.IsActive && p.OrganizationId == orgId).ToList(); + var globalOptionData = _optionRepository.AsQueryable().Where(s => s.IsEnabled); + var localpersonalData = _localOptionRepository.AsQueryable().Where(p => p.IsActive && p.OrganizationId == orgId).ToList(); - List result = new List(); + var result = new List(); result.AddRange(globalOptionData.AsQueryable().Where(s => s.IsObligatory)); foreach (var p in localpersonalData) @@ -56,51 +53,28 @@ public virtual IHttpActionResult GetOptionsByObjectIDAndType(int id, EntityType } } - var options = GetAttachedOptions(optiontype, id, entitytype); - - if (options != null) - { - if (options.Count() <= 0) - { - return Ok(result); - } - foreach (var o in options) - { - var currentOption = result.FirstOrDefault(r => r.Id == o.OptionId); - if (currentOption != null) - { - result.FirstOrDefault(r => r.Id == o.OptionId).Checked = true; - } - else - { - _AttachedOptionRepository.Delete(o); - _AttachedOptionRepository.Save(); - } - } - } - else - { - return StatusCode(HttpStatusCode.NoContent); - } - - return Ok(result); + return CreateOptionsResponse(id, entitytype, optiontype, result); } - [System.Web.Http.HttpDelete] + [HttpDelete] [EnableQuery] [ODataRoute("RemoveOption(id={id}, objectId={objectId}, type={type}, entityType={entityType})")] public IHttpActionResult RemoveOption(int id, int objectId, OptionType type, EntityType entityType) { - var option = _AttachedOptionRepository.AsQueryable().FirstOrDefault(o => o.OptionId == id + var option = _attachedOptionRepository.AsQueryable().FirstOrDefault(o => o.OptionId == id && o.ObjectId == objectId && o.OptionType == type && o.ObjectType == entityType); if (option == null) + { return NotFound(); + } - if (!_authService.HasWriteAccess(UserId, option)) - return Unauthorized(); + if (!AuthService.HasWriteAccess(UserId, option)) + { + return Forbidden(); + } try { @@ -115,27 +89,56 @@ public IHttpActionResult RemoveOption(int id, int objectId, OptionType type, Ent return StatusCode(HttpStatusCode.NoContent); } - private List GetAttachedOptions(OptionType type, int id, EntityType objectType) + protected IHttpActionResult CreateOptionsResponse(int id, EntityType entitytype, OptionType optiontype, List result) { - var hasOrg = typeof(IHasOrganization).IsAssignableFrom(typeof(AttachedOption)); + var options = GetAttachedOptions(optiontype, id, entitytype); - if (_authService.HasReadAccessOutsideContext(UserId) || hasOrg == false) + if (options != null) + { + if (!options.Any()) { - //tolist so we can operate with open datareaders in the following foreach loop. - return _AttachedOptionRepository.AsQueryable().Where(x => x.ObjectId == id - && x.OptionType == type - && x.ObjectType == objectType).ToList(); + return Ok(result); } - else + + foreach (var o in options) { - return _AttachedOptionRepository.AsQueryable() - .Where(x => ((IHasOrganization)x).OrganizationId == _authService.GetCurrentOrganizationId(UserId) - && x.ObjectId == id - && x.OptionType == type - && x.ObjectType == objectType).ToList(); + var currentOption = result.FirstOrDefault(r => r.Id == o.OptionId); + if (currentOption != null) + { + currentOption.Checked = true; + } + else + { + _attachedOptionRepository.Delete(o); + _attachedOptionRepository.Save(); + } } } - } + else + { + return StatusCode(HttpStatusCode.NoContent); + } + return Ok(result); + } + protected List GetAttachedOptions(OptionType type, int id, EntityType objectType) + { + var hasOrg = typeof(IHasOrganization).IsAssignableFrom(typeof(AttachedOption)); + + if (AuthService.HasReadAccessOutsideContext(UserId) || hasOrg == false) + { + //tolist so we can operate with open datareaders in the following foreach loop. + return _attachedOptionRepository.AsQueryable().Where(x => x.ObjectId == id + && x.OptionType == type + && x.ObjectType == objectType).ToList(); + } + + return _attachedOptionRepository.AsQueryable() + .Where(x => ((IHasOrganization)x).OrganizationId == AuthService.GetCurrentOrganizationId(UserId) + && x.ObjectId == id + && x.OptionType == type + && x.ObjectType == objectType).ToList(); + } + } } \ No newline at end of file diff --git a/Presentation.Web/Controllers/OData/AttachedOptions/Itsystem/AttachedOptionsRegularPersonalDataController.cs b/Presentation.Web/Controllers/OData/AttachedOptions/Itsystem/AttachedOptionsRegularPersonalDataController.cs index da18992e40..6c067fb52f 100644 --- a/Presentation.Web/Controllers/OData/AttachedOptions/Itsystem/AttachedOptionsRegularPersonalDataController.cs +++ b/Presentation.Web/Controllers/OData/AttachedOptions/Itsystem/AttachedOptionsRegularPersonalDataController.cs @@ -2,33 +2,39 @@ using Core.DomainModel; using Core.DomainModel.ItSystem; using Core.DomainServices; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; using System.Web.Http; -using System.Web.Mvc; using System.Web.OData; using System.Web.OData.Routing; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.AttachedOptions { - using System.Net; - + [InternalApi] public class AttachedOptionsRegularPersonalDataController : AttachedOptionsFunctionController { - public AttachedOptionsRegularPersonalDataController(IGenericRepository repository, IAuthenticationService authService, + public AttachedOptionsRegularPersonalDataController( + IGenericRepository repository, + IAuthenticationService authService, IGenericRepository regularPersonalDataTypeRepository, IGenericRepository localregularPersonalDataTypeRepository) - : base(repository, authService, regularPersonalDataTypeRepository, - localregularPersonalDataTypeRepository){} + : base(repository, authService, regularPersonalDataTypeRepository,localregularPersonalDataTypeRepository){ + + } + + [System.Web.Http.HttpGet] + [EnableQuery] + [ODataRoute("GetRegularPersonalDataBySystemId(id={id})")] + public IHttpActionResult GetRegularPersonalDataBySystemId(int id) + { + return GetOptionsByObjectIDAndType(id, EntityType.ITSYSTEM, OptionType.REGULARPERSONALDATA); + } [System.Web.Http.HttpGet] [EnableQuery] - [ODataRoute("GetRegularPersonalDataByObjectID(id={id},entitytype={entitytype})")] - public IHttpActionResult GetOptionsByObjectID(int id, EntityType entitytype) + [ODataRoute("GetRegularPersonalDataByUsageId(id={id})")] + public IHttpActionResult GetRegularPersonalDataByUsageId(int id) { - return base.GetOptionsByObjectIDAndType(id, entitytype, OptionType.REGULARPERSONALDATA); + return GetOptionsByObjectIDAndType(id, EntityType.ITSYSTEMUSAGE, OptionType.REGULARPERSONALDATA); } } } \ No newline at end of file diff --git a/Presentation.Web/Controllers/OData/AttachedOptions/Itsystem/AttachedOptionsSensitivePersonalDataController.cs b/Presentation.Web/Controllers/OData/AttachedOptions/Itsystem/AttachedOptionsSensitivePersonalDataController.cs index e623fe96a9..2231a36c62 100644 --- a/Presentation.Web/Controllers/OData/AttachedOptions/Itsystem/AttachedOptionsSensitivePersonalDataController.cs +++ b/Presentation.Web/Controllers/OData/AttachedOptions/Itsystem/AttachedOptionsSensitivePersonalDataController.cs @@ -2,33 +2,39 @@ using Core.DomainModel; using Core.DomainModel.ItSystem; using Core.DomainServices; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; using System.Web.Http; -using System.Web.Mvc; using System.Web.OData; using System.Web.OData.Routing; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.AttachedOptions { - using System.Net; - + [InternalApi] public class AttachedOptionsSensitivePersonalDataController : AttachedOptionsFunctionController { - public AttachedOptionsSensitivePersonalDataController(IGenericRepository repository, - IGenericRepository localSensitivePersonalDataTypeRepository, IAuthenticationService authService, + public AttachedOptionsSensitivePersonalDataController( + IGenericRepository repository, + IGenericRepository localSensitivePersonalDataTypeRepository, + IAuthenticationService authService, IGenericRepository sensitiveDataTypeRepository) - : base(repository, authService, sensitiveDataTypeRepository, - localSensitivePersonalDataTypeRepository){} + : base(repository, authService, sensitiveDataTypeRepository, localSensitivePersonalDataTypeRepository) + { + } + + [HttpGet] + [EnableQuery] + [ODataRoute("GetSensitivePersonalDataByUsageId(id={id})")] + public IHttpActionResult GetSensitivePersonalDataByUsageId(int id) + { + return GetOptionsByObjectIDAndType(id, EntityType.ITSYSTEMUSAGE, OptionType.SENSITIVEPERSONALDATA); + } - [System.Web.Http.HttpGet] + [HttpGet] [EnableQuery] - [ODataRoute("GetSensitivePersonalDataByObjectID(id={id}, entitytype={entitytype})")] - public IHttpActionResult GetOptionsByObjectID(int id, EntityType entitytype) + [ODataRoute("GetSensitivePersonalDataBySystemId(id={id})")] + public IHttpActionResult GetSensitivePersonalDataBySystemId(int id) { - return base.GetOptionsByObjectIDAndType(id,entitytype, OptionType.SENSITIVEPERSONALDATA); + return GetOptionsByObjectIDAndType(id, EntityType.ITSYSTEM, OptionType.SENSITIVEPERSONALDATA); } } } \ No newline at end of file diff --git a/Presentation.Web/Controllers/OData/AttachedOptions/ItsystemUsage/AttachedOptionsRegisterTypesController.cs b/Presentation.Web/Controllers/OData/AttachedOptions/ItsystemUsage/AttachedOptionsRegisterTypesController.cs index 02c66f6217..7bba2e5f42 100644 --- a/Presentation.Web/Controllers/OData/AttachedOptions/ItsystemUsage/AttachedOptionsRegisterTypesController.cs +++ b/Presentation.Web/Controllers/OData/AttachedOptions/ItsystemUsage/AttachedOptionsRegisterTypesController.cs @@ -3,32 +3,41 @@ using Core.DomainModel.ItSystem; using Core.DomainModel.ItSystemUsage; using Core.DomainServices; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Web; +using System.Threading.Tasks; using System.Web.Http; -using System.Web.Mvc; using System.Web.OData; using System.Web.OData.Routing; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.AttachedOptions { - public class AttachedOptionsRegisterTypesController : AttachedOptionsFunctionController + [InternalApi] + public class AttachedOptionsRegisterTypesController : AttachedOptionsFunctionController { - public AttachedOptionsRegisterTypesController(IGenericRepository repository, + public AttachedOptionsRegisterTypesController( + IGenericRepository repository, IAuthenticationService authService, IGenericRepository registerTypeRepository, IGenericRepository localRegisterTypeRepository) - : base(repository, authService, registerTypeRepository, localRegisterTypeRepository){} + : base(repository, authService, registerTypeRepository, localRegisterTypeRepository) + { - [System.Web.Http.HttpGet] - [EnableQuery] - [ODataRoute("GetRegisterTypesByObjectID(id={id})")] - public IHttpActionResult GetOptionsByObjectID(int id) - { - return base.GetOptionsByObjectIDAndType(id, EntityType.ITSYSTEMUSAGE, OptionType.REGISTERTYPEDATA); - } + } + + [HttpGet] + [EnableQuery] + [ODataRoute("GetRegisterTypesByObjectID(id={id})")] + public IHttpActionResult GetRegisterTypesByObjectID(int id) + { + return GetOptionsByObjectIDAndType(id, EntityType.ITSYSTEMUSAGE, OptionType.REGISTERTYPEDATA); + } + + + [System.Web.Http.HttpPost] + [ODataRoute("AttachedOptions")] + public async Task Post([FromODataUri] int key, AttachedOption dto) + { + return base.Post(dto); + } } } \ No newline at end of file diff --git a/Presentation.Web/Controllers/OData/BaseController.cs b/Presentation.Web/Controllers/OData/BaseController.cs index 3e57aaf669..e97e530120 100644 --- a/Presentation.Web/Controllers/OData/BaseController.cs +++ b/Presentation.Web/Controllers/OData/BaseController.cs @@ -1,14 +1,14 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Net.Http; using System.Web.Http; using System.Web.OData; using System.Web.OData.Extensions; using Core.DomainServices; using System.Web.OData.Routing; -using Microsoft.OData.Core; -using Microsoft.OData.Core.UriParser; +using Microsoft.OData.UriParser; using Ninject; using Ninject.Extensions.Logging; using System.Web.Http.Routing; @@ -60,22 +60,26 @@ protected TKey GetKeyFromUri(HttpRequestMessage request, Uri uri) } var urlHelper = request.GetUrlHelper() ?? new UrlHelper(request); + var pathHandler = (IODataPathHandler)request.GetRequestContainer().GetService(typeof(IODataPathHandler)); - string serviceRoot = urlHelper.CreateODataLink( - request.ODataProperties().RouteName, - request.ODataProperties().PathHandler, new List()); - var odataPath = request.ODataProperties().PathHandler.Parse( - request.ODataProperties().Model, - serviceRoot, uri.LocalPath); + string serviceRoot = urlHelper.CreateODataLink(request.ODataProperties().RouteName,pathHandler, new List()); - var keySegment = odataPath.Segments.OfType().FirstOrDefault(); + var odataPath = pathHandler.Parse(serviceRoot,uri.LocalPath, request.GetRequestContainer()); + + var keySegment = odataPath.Segments.OfType().FirstOrDefault(); if (keySegment == null) { throw new InvalidOperationException("The link does not contain a key."); } - var value = ODataUriUtils.ConvertFromUriLiteral(keySegment.Value, ODataVersion.V4); + var value = keySegment.Keys.FirstOrDefault().Value; return (TKey)value; } + + protected virtual IHttpActionResult Forbidden() + { + return ResponseMessage(new HttpResponseMessage(HttpStatusCode.Forbidden)); + } + } } diff --git a/Presentation.Web/Controllers/OData/BaseEntityController.cs b/Presentation.Web/Controllers/OData/BaseEntityController.cs index 7d124b06d0..110a772380 100644 --- a/Presentation.Web/Controllers/OData/BaseEntityController.cs +++ b/Presentation.Web/Controllers/OData/BaseEntityController.cs @@ -6,48 +6,46 @@ using System; using Core.DomainModel; using System.Linq; +using Core.ApplicationServices.Authorization; +using Core.DomainServices.Authorization; +using Core.DomainServices.Queries; using Ninject.Infrastructure.Language; +using Presentation.Web.Infrastructure.Authorization.Controller; namespace Presentation.Web.Controllers.OData { - public abstract class BaseEntityController : BaseController where T : class, IEntity + public abstract class BaseEntityController : BaseController where T : class, IEntity { - private readonly IAuthenticationService _authService; + protected IAuthenticationService AuthService { get; } //TODO: Remove once the new approach is validated + private readonly IControllerAuthorizationStrategy _authorizationStrategy; - protected BaseEntityController(IGenericRepository repository, IAuthenticationService authService) + protected BaseEntityController( + IGenericRepository repository, + IAuthenticationService authService, + IAuthorizationContext authorizationContext = null) : base(repository) { - _authService = authService; + _authorizationStrategy = + authorizationContext == null + ? (IControllerAuthorizationStrategy)new LegacyAuthorizationStrategy(authService, () => UserId) + : new ContextBasedAuthorizationStrategy(authorizationContext); + AuthService = authService; } [EnableQuery] public override IHttpActionResult Get() { - if (UserId == 0) - return Unauthorized(); + var organizationId = AuthService.GetCurrentOrganizationId(UserId); - var hasOrg = typeof(IHasOrganization).IsAssignableFrom(typeof(T)); - var hasAccessModifier = typeof(IHasAccessModifier).IsAssignableFrom(typeof(T)); + var crossOrganizationReadAccess = GetCrossOrganizationReadAccessLevel(); - var result = Repository.AsQueryable(); + var refinement = new QueryAllByRestrictionCapabilities(crossOrganizationReadAccess, organizationId); - if (_authService.HasReadAccessOutsideContext(UserId) || hasOrg == false) - { - if (hasAccessModifier && !_authService.IsGlobalAdmin(UserId)) - { - if (hasOrg) - { - result = result.ToEnumerable().Where(x => ((IHasAccessModifier)x).AccessModifier == AccessModifier.Public || ((IHasOrganization)x).OrganizationId == _authService.GetCurrentOrganizationId(UserId)).AsQueryable(); - } - else - { - result = result.ToEnumerable().Where(x => ((IHasAccessModifier)x).AccessModifier == AccessModifier.Public).AsQueryable(); - } - } - } - else + var result = refinement.Apply(Repository.AsQueryable()); + + if (refinement.RequiresPostFiltering()) { - result = result.ToEnumerable().Where(x => ((IHasOrganization) x).OrganizationId == _authService.GetCurrentOrganizationId(UserId)).AsQueryable(); + result = result.ToEnumerable().Where(AllowRead).AsQueryable(); } return Ok(result); @@ -58,12 +56,16 @@ public override IHttpActionResult Get(int key) { var result = Repository.AsQueryable().Where(p => p.Id == key); - if (!result.Any()) + if (result.Any() == false) + { return NotFound(); + } var entity = result.First(); - if (!_authService.HasReadAccess(UserId, entity)) - return Unauthorized(); + if (AllowRead(entity) == false) + { + return Forbidden(); + } return Ok(SingleResult.Create(result)); } @@ -72,37 +74,47 @@ public override IHttpActionResult Get(int key) public IHttpActionResult GetByOrganizationKey(int key) { if (typeof(IHasOrganization).IsAssignableFrom(typeof(T)) == false) - throw new InvalidCastException("Entity must implement IHasOrganization"); + { + return BadRequest("Entity does not belong to an organization"); + } - var loggedIntoOrgId = _authService.GetCurrentOrganizationId(UserId); - if (loggedIntoOrgId != key && !_authService.HasReadAccessOutsideContext(UserId)) - return Unauthorized(); + var accessLevel = GetOrganizationReadAccessLevel(key); - var result = Repository.AsQueryable().Where(m => ((IHasOrganization)m).OrganizationId == key); - return Ok(result); - } + if (accessLevel == OrganizationDataReadAccessLevel.None) + { + return Forbidden(); + } - public IHttpActionResult Put(int key, T entity) - { - return StatusCode(HttpStatusCode.NotImplemented); + var entities = QueryFactory.ByOrganizationId(key, accessLevel).Apply(Repository.AsQueryable()); + + return Ok(entities); } + [System.Web.Http.Description.ApiExplorerSettings] public virtual IHttpActionResult Post(T entity) { if (!ModelState.IsValid) + { return BadRequest(ModelState); + } - if (entity is IHasOrganization && (entity as IHasOrganization).OrganizationId == 0) + //Make sure organization dependent entity is assigned to the active organization if no explicit organization is provided + if (entity is IHasOrganization organization && organization.OrganizationId == 0) { - (entity as IHasOrganization).OrganizationId = _authService.GetCurrentOrganizationId(UserId); + organization.OrganizationId = AuthService.GetCurrentOrganizationId(UserId); } entity.ObjectOwnerId = UserId; entity.LastChangedByUserId = UserId; - if (!_authService.HasWriteAccess(UserId, entity)) + if (AllowCreate(entity) == false) { - return Unauthorized(); + return Forbidden(); + } + + if ((entity as IHasAccessModifier)?.AccessModifier == AccessModifier.Public && AllowEntityVisibilityControl(entity) == false) + { + return Forbidden(); } try @@ -118,22 +130,34 @@ public virtual IHttpActionResult Post(T entity) return Created(entity); } + [System.Web.Http.Description.ApiExplorerSettings] public virtual IHttpActionResult Patch(int key, Delta delta) { var entity = Repository.GetByKey(key); - // does the entity exist? if (entity == null) + { return NotFound(); + } // check if user is allowed to write to the entity - if (!_authService.HasWriteAccess(UserId, entity)) - return StatusCode(HttpStatusCode.Forbidden); + if (AllowWrite(entity) == false) + { + return Forbidden(); + } + + if (delta.TryGetPropertyValue(nameof(IHasAccessModifier.AccessModifier), out object accessModifier) && + accessModifier.Equals(AccessModifier.Public) && AllowEntityVisibilityControl(entity) == false) + { + return Forbidden(); + } // check model state if (!ModelState.IsValid) + { return BadRequest(ModelState); + } try { @@ -156,10 +180,14 @@ public virtual IHttpActionResult Delete(int key) { var entity = Repository.GetByKey(key); if (entity == null) + { return NotFound(); + } - if (!_authService.HasWriteAccess(UserId, entity)) - return Unauthorized(); + if (AllowDelete(entity) == false) + { + return Forbidden(); + } try { @@ -173,5 +201,45 @@ public virtual IHttpActionResult Delete(int key) return StatusCode(HttpStatusCode.NoContent); } + + protected CrossOrganizationDataReadAccessLevel GetCrossOrganizationReadAccessLevel() + { + return _authorizationStrategy.GetCrossOrganizationReadAccess(); + } + + protected OrganizationDataReadAccessLevel GetOrganizationReadAccessLevel(int organizationId) + { + return _authorizationStrategy.GetOrganizationReadAccessLevel(organizationId); + } + + protected bool AllowRead(T entity) + { + return _authorizationStrategy.AllowRead(entity); + } + + protected bool AllowWrite(T entity) + { + return _authorizationStrategy.AllowModify(entity); + } + + protected bool AllowCreate() + { + return _authorizationStrategy.AllowCreate(); + } + + protected bool AllowCreate(IEntity entity) + { + return _authorizationStrategy.AllowCreate(entity); + } + + protected bool AllowDelete(IEntity entity) + { + return _authorizationStrategy.AllowDelete(entity); + } + + protected bool AllowEntityVisibilityControl(IEntity entity) + { + return _authorizationStrategy.AllowEntityVisibilityControl(entity); + } } } diff --git a/Presentation.Web/Controllers/OData/BaseOptionController.cs b/Presentation.Web/Controllers/OData/BaseOptionController.cs index 04a1d4951f..863f8a7eba 100644 --- a/Presentation.Web/Controllers/OData/BaseOptionController.cs +++ b/Presentation.Web/Controllers/OData/BaseOptionController.cs @@ -13,10 +13,10 @@ public abstract class BaseOptionController : BaseEntity where TType : OptionEntity { - private IGenericRepository _repository; - private IAuthenticationService _authService; + private readonly IGenericRepository _repository; + private readonly IAuthenticationService _authService; // GET: BaseRole - public BaseOptionController(IGenericRepository repository, IAuthenticationService authService) + protected BaseOptionController(IGenericRepository repository, IAuthenticationService authService) : base(repository, authService) { _repository = repository; @@ -35,7 +35,7 @@ public override IHttpActionResult Patch(int key, Delta delta) if (t.ToLower() == "priority") { - var initDelta = delta.GetEntity(); + var initDelta = delta.GetInstance(); var entity = _repository.GetByKey(key); @@ -85,7 +85,7 @@ public override IHttpActionResult Post(TType entity) { var Entities = _repository.Get(); - if(Entities.Count() > 0) + if(Entities.Any()) { entity.Priority = _repository.Get().Max(e => e.Priority) + 1; }else @@ -109,7 +109,9 @@ public override IHttpActionResult Delete(int key) return NotFound(); if (!_authService.HasWriteAccess(UserId, entity)) - return Unauthorized(); + { + return Forbidden(); + } var liste = _repository.Get().Where(o => o.Id != key).OrderBy(o => o.Priority); try @@ -134,7 +136,6 @@ public override IHttpActionResult Delete(int key) catch (Exception ex) { Logger.Error(ex, "Could not reprioritize!"); - //return InternalServerError(ex); } return StatusCode(HttpStatusCode.NoContent); diff --git a/Presentation.Web/Controllers/OData/BaseRoleController.cs b/Presentation.Web/Controllers/OData/BaseRoleController.cs deleted file mode 100644 index cb3608dea8..0000000000 --- a/Presentation.Web/Controllers/OData/BaseRoleController.cs +++ /dev/null @@ -1,102 +0,0 @@ -using Core.ApplicationServices; -using Core.DomainModel; -using Core.DomainServices; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.Http; -using System.Web.Mvc; -using System.Web.OData; - -namespace Presentation.Web.Controllers.OData -{ - public abstract class BaseRoleController : BaseEntityController where T : OptionEntity - { - - private IGenericRepository _repository; - // GET: BaseRole - public BaseRoleController(IGenericRepository repository, IAuthenticationService authService) - : base(repository, authService) - { - _repository = repository; - } - public override IHttpActionResult Patch(int key, Delta delta) - { - - foreach (var t in delta.GetChangedPropertyNames()) - { - - if (t.ToLower() == "priority") - { - //var testPriorFromDelta = 1; - var initDelta = delta.GetEntity(); - var entity = _repository.GetByKey(key); - - if (entity.priority != 0) - { - - if (initDelta.priority > entity.priority) - { - - var entityToBeChanged = _repository.Get().FirstOrDefault(x => x.priority == entity.priority + 1); - - if (entityToBeChanged != null) - { - entityToBeChanged.priority = entityToBeChanged.priority - 1; - _repository.Update(entityToBeChanged); - _repository.Save(); - } - else - { - if (entity.priority > 0) - initDelta.priority = entity.priority; - } - } - else - { - var entityToBeChanged = _repository.Get().FirstOrDefault(x => x.priority == entity.priority - 1); - - if (entityToBeChanged != null) - { - entityToBeChanged.priority = entityToBeChanged.priority + 1; - _repository.Update(entityToBeChanged); - _repository.Save(); - } - else - { - initDelta.priority = entity.priority; - } - } - break; - } - else - { - if (delta.GetEntity().priority > entity.priority) - { - var entitiesToBeChanged = _repository.Get(x => x.priority >= initDelta.priority); - - if (entitiesToBeChanged.Count() > 0) - { - foreach (var e in entitiesToBeChanged) - { - e.priority = e.priority + 1; - _repository.Update(e); - _repository.Save(); - } - } - else - { - if (entity.priority >= 1) - { - initDelta.priority = entity.priority; - } - } - } - } - } - } - return base.Patch(key, delta); - } - } -} \ No newline at end of file diff --git a/Presentation.Web/Controllers/OData/ConfigsController.cs b/Presentation.Web/Controllers/OData/ConfigsController.cs index eb4b2cfa5f..f3bf9ca77f 100644 --- a/Presentation.Web/Controllers/OData/ConfigsController.cs +++ b/Presentation.Web/Controllers/OData/ConfigsController.cs @@ -1,8 +1,10 @@ using Core.ApplicationServices; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData { + [PublicApi] public class ConfigsController : BaseEntityController { public ConfigsController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/EconomyStreamsController.cs b/Presentation.Web/Controllers/OData/EconomyStreamsController.cs index 17b155ffb8..96b8a144f6 100644 --- a/Presentation.Web/Controllers/OData/EconomyStreamsController.cs +++ b/Presentation.Web/Controllers/OData/EconomyStreamsController.cs @@ -1,22 +1,29 @@ using System; using System.Linq; +using System.Net; using System.Web.Http; using System.Web.OData; using System.Web.OData.Query; using System.Web.OData.Routing; +using Core.ApplicationServices; using Core.DomainModel; using Core.DomainModel.ItContract; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; +using Swashbuckle.OData; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.OData { [Authorize] - public class EconomyStreamsController : ODataController // doesn't derive from BaseEntityController because we need absolute control over what is exposed here + [PublicApi] + public class EconomyStreamsController : BaseEntityController +// doesn't derive from BaseEntityController because we need absolute control over what is exposed here { private readonly IGenericRepository _repository; private readonly IGenericRepository _userRepository; - public EconomyStreamsController(IGenericRepository repository, IGenericRepository userRepository) + public EconomyStreamsController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository userRepository) : base(repository, authService) { _repository = repository; _userRepository = userRepository; @@ -25,6 +32,8 @@ public EconomyStreamsController(IGenericRepository repository, IG // GET /Organizations(1)/ItContracts [EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All & ~AllowedQueryOptions.Expand)] [ODataRoute("ExternEconomyStreams(Organization={orgKey})")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse>))] + [SwaggerResponse(HttpStatusCode.Forbidden)] public IHttpActionResult GetByOrganization(int orgKey) { var result = @@ -41,10 +50,14 @@ public IHttpActionResult GetByOrganization(int orgKey) var contractId = economyStream.ExternPaymentFor.Id; if (!HasAccessWithinOrganization(orgKey) && !EconomyStreamIsPublic(contractId)) - return Unauthorized(); + { + return Forbidden(); + } } else if (!HasAccessWithinOrganization(orgKey)) - return Unauthorized(); + { + return Forbidden(); + } return Ok(result); } @@ -52,10 +65,14 @@ public IHttpActionResult GetByOrganization(int orgKey) // GET /Organizations(1)/ItContracts(1)/ExternEconomyStreams [EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All & ~AllowedQueryOptions.Expand)] [ODataRoute("Organizations({orgKey})/ItContracts({contractKey})/ExternEconomyStreams")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse>))] + [SwaggerResponse(HttpStatusCode.Forbidden)] public IHttpActionResult GetAllExtern(int orgKey, int contractKey) { if (!HasAccessWithinOrganization(orgKey) && !EconomyStreamIsPublic(contractKey)) - return Unauthorized(); + { + return Forbidden(); + } var result = _repository.AsQueryable() @@ -69,10 +86,14 @@ public IHttpActionResult GetAllExtern(int orgKey, int contractKey) // GET /Organizations(1)/ItContracts(1)/InternEconomyStreams [EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All & ~AllowedQueryOptions.Expand)] [ODataRoute("Organizations({orgKey})/ItContracts({contractKey})/InternEconomyStreams")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse>))] + [SwaggerResponse(HttpStatusCode.Forbidden)] public IHttpActionResult GetAllIntern(int orgKey, int contractKey) { if (!HasAccessWithinOrganization(orgKey) && !EconomyStreamIsPublic(contractKey)) - return Unauthorized(); + { + return Forbidden(); + } var result = _repository.AsQueryable() @@ -86,10 +107,14 @@ public IHttpActionResult GetAllIntern(int orgKey, int contractKey) // GET /Organizations(1)/ItContracts(1)/ExternEconomyStreams(1) [EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All & ~AllowedQueryOptions.Expand)] [ODataRoute("Organizations({orgKey})/ItContracts({contractKey})/ExternEconomyStreams({key})")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse>))] + [SwaggerResponse(HttpStatusCode.Forbidden)] public IHttpActionResult GetSingleExtern(int orgKey, int contractKey, int key) { if (!HasAccessWithinOrganization(orgKey) && !EconomyStreamIsPublic(contractKey)) - return Unauthorized(); + { + return Forbidden(); + } var result = _repository.AsQueryable() @@ -104,10 +129,14 @@ public IHttpActionResult GetSingleExtern(int orgKey, int contractKey, int key) // GET /Organizations(1)/ItContracts(1)/InternEconomyStreams(1) [EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All & ~AllowedQueryOptions.Expand)] [ODataRoute("Organizations({orgKey})/ItContracts({contractKey})/InternEconomyStreams({key})")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse>))] + [SwaggerResponse(HttpStatusCode.Forbidden)] public IHttpActionResult GetSingleIntern(int orgKey, int contractKey, int key) { if (!HasAccessWithinOrganization(orgKey) && !EconomyStreamIsPublic(contractKey)) - return Unauthorized(); + { + return Forbidden(); + } var result = _repository.AsQueryable() diff --git a/Presentation.Web/Controllers/OData/GlobalConfigsController.cs b/Presentation.Web/Controllers/OData/GlobalConfigsController.cs index d6ed81f6c5..a0a993d18a 100644 --- a/Presentation.Web/Controllers/OData/GlobalConfigsController.cs +++ b/Presentation.Web/Controllers/OData/GlobalConfigsController.cs @@ -1,8 +1,10 @@ using Core.ApplicationServices; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData { + [PublicApi] public class GlobalConfigsController : BaseEntityController { public GlobalConfigsController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/HelpTextsController.cs b/Presentation.Web/Controllers/OData/HelpTextsController.cs index 7a29469028..d7011aec92 100644 --- a/Presentation.Web/Controllers/OData/HelpTextsController.cs +++ b/Presentation.Web/Controllers/OData/HelpTextsController.cs @@ -1,9 +1,11 @@ using Core.ApplicationServices; using Core.DomainServices; using Core.DomainModel; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData { + [PublicApi] public class HelpTextsController : BaseEntityController { public HelpTextsController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/ItContractAgreementElementTypesController.cs b/Presentation.Web/Controllers/OData/ItContractAgreementElementTypesController.cs index c88582630f..aefcc8105c 100644 --- a/Presentation.Web/Controllers/OData/ItContractAgreementElementTypesController.cs +++ b/Presentation.Web/Controllers/OData/ItContractAgreementElementTypesController.cs @@ -1,5 +1,6 @@ using Core.DomainModel.ItContract; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData { @@ -7,6 +8,7 @@ namespace Presentation.Web.Controllers.OData /// Gives access to relations between ItContract and ElementTypes /// Primarily used for reporting /// + [PublicApi] public class ItContractAgreementElementTypesController : BaseController { public ItContractAgreementElementTypesController(IGenericRepository repository) diff --git a/Presentation.Web/Controllers/OData/ItContractItSystemUsagesController.cs b/Presentation.Web/Controllers/OData/ItContractItSystemUsagesController.cs index 83cabd28dc..4efd19619e 100644 --- a/Presentation.Web/Controllers/OData/ItContractItSystemUsagesController.cs +++ b/Presentation.Web/Controllers/OData/ItContractItSystemUsagesController.cs @@ -1,5 +1,6 @@ using Core.DomainModel.ItContract; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData { @@ -7,6 +8,7 @@ namespace Presentation.Web.Controllers.OData /// Gives access to relations between ItContract and ItSystemUsage /// Primarily used for reporting /// + [PublicApi] public class ItContractItSystemUsagesController : BaseController { public ItContractItSystemUsagesController(IGenericRepository repository) diff --git a/Presentation.Web/Controllers/OData/ItContractRightsController.cs b/Presentation.Web/Controllers/OData/ItContractRightsController.cs index d7af1fff75..2ca6263f8a 100644 --- a/Presentation.Web/Controllers/OData/ItContractRightsController.cs +++ b/Presentation.Web/Controllers/OData/ItContractRightsController.cs @@ -5,12 +5,16 @@ using Core.DomainServices; using Core.DomainModel.ItContract; using Core.ApplicationServices; +using Presentation.Web.Infrastructure.Attributes; +using Swashbuckle.OData; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.OData { using System; using System.Net; + [PublicApi] public class ItContractRightsController : BaseEntityController { private IAuthenticationService _authService; @@ -23,6 +27,7 @@ public ItContractRightsController(IGenericRepository repository // GET /Organizations(1)/ItContracts(1)/Rights [EnableQuery] [ODataRoute("Organizations({orgId})/ItContracts({contractId})/Rights")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse>))] public IHttpActionResult GetByItContract(int orgId, int contractId) { // TODO figure out how to check auth @@ -33,6 +38,7 @@ public IHttpActionResult GetByItContract(int orgId, int contractId) // GET /Users(1)/ItContractRights [EnableQuery] [ODataRoute("Users({userId})/ItContractRights")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse>))] public IHttpActionResult GetByUser(int userId) { // TODO figure out how to check auth @@ -45,10 +51,14 @@ public override IHttpActionResult Delete(int key) var entity = Repository.GetByKey(key); var test = !_authService.IsLocalAdmin(this.UserId); if (entity == null) + { return NotFound(); + } if (!_authService.HasWriteAccess(UserId, entity) && !_authService.IsLocalAdmin(this.UserId)) - return Unauthorized(); + { + return Forbidden(); + } try { @@ -74,18 +84,15 @@ public override IHttpActionResult Patch(int key, Delta delta) // check if user is allowed to write to the entity if (!_authService.HasWriteAccess(UserId, entity) && !_authService.IsLocalAdmin(this.UserId)) - return StatusCode(HttpStatusCode.Forbidden); - - //Check if user is allowed to set accessmodifier to public - //var accessModifier = (entity as IHasAccessModifier)?.AccessModifier; - //if (accessModifier == AccessModifier.Public && !_authService.CanExecute(UserId, Feature.CanSetAccessModifierToPublic)) - //{ - // return Unauthorized(); - //} + { + return Forbidden(); + } // check model state if (!ModelState.IsValid) + { return BadRequest(ModelState); + } try { diff --git a/Presentation.Web/Controllers/OData/ItContractsController.cs b/Presentation.Web/Controllers/OData/ItContractsController.cs index a4839689d8..fcab69891b 100644 --- a/Presentation.Web/Controllers/OData/ItContractsController.cs +++ b/Presentation.Web/Controllers/OData/ItContractsController.cs @@ -1,19 +1,22 @@ using System.Collections.Generic; using System.Data.Entity; using System.Linq; +using System.Net; using System.Web.Http; using System.Web.OData; using System.Web.OData.Routing; using Core.DomainModel.ItContract; using Core.DomainServices; -using System.Net; using Core.DomainModel.Organization; using Core.ApplicationServices; -using System; -using Infrastructure.DataAccess; +using Core.DomainServices.Extensions; +using Presentation.Web.Infrastructure.Attributes; +using Swashbuckle.OData; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.OData { + [PublicApi] public class ItContractsController : BaseEntityController { private readonly IGenericRepository _orgUnitRepository; @@ -26,8 +29,14 @@ public ItContractsController(IGenericRepository repository, IGeneric _authService = authService; } + /// + /// Hvis den autentificerede bruger er Global Admin, returneres alle kontrakter. + /// Ellers returneres organisationens kontrakter. + /// + /// [EnableQuery] [ODataRoute("ItContracts")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse>))] public override IHttpActionResult Get() { var orgId = _authService.GetCurrentOrganizationId(UserId); @@ -39,84 +48,125 @@ public override IHttpActionResult Get() // GET /ItContracts(1)/ResponsibleOrganizationUnit [EnableQuery] [ODataRoute("ItContracts({contractKey})/ResponsibleOrganizationUnit")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse))] + [SwaggerResponse(HttpStatusCode.Forbidden)] + [SwaggerResponse(HttpStatusCode.NotFound)] public IHttpActionResult GetResponsibleOrganizationUnit(int contractKey) { var entity = Repository.GetByKey(contractKey).ResponsibleOrganizationUnit; if (entity == null) + { return NotFound(); + } if (_authService.HasReadAccess(UserId, entity)) + { return Ok(entity); + } - return StatusCode(HttpStatusCode.Forbidden); + return Forbidden(); } // GET /ItContracts(1)/ResponsibleOrganizationUnit [EnableQuery] [ODataRoute("ItContracts({contractKey})/Organization")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse))] + [SwaggerResponse(HttpStatusCode.Forbidden)] + [SwaggerResponse(HttpStatusCode.NotFound)] public IHttpActionResult GetOrganization(int contractKey) { var entity = Repository.GetByKey(contractKey).Organization; if (entity == null) + { return NotFound(); + } if (_authService.HasReadAccess(UserId, entity)) + { return Ok(entity); + } - return StatusCode(HttpStatusCode.Forbidden); + return Forbidden(); } - // GET /Organizations(1)/ItContracts + /// + /// Henter alle organisationens IT Kontrakter + /// + /// + /// [EnableQuery(MaxExpansionDepth = 3)] [ODataRoute("Organizations({key})/ItContracts")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse>))] + [SwaggerResponse(HttpStatusCode.Forbidden)] public IHttpActionResult GetItContracts(int key) { var loggedIntoOrgId = _authService.GetCurrentOrganizationId(UserId); if (loggedIntoOrgId != key && !_authService.HasReadAccessOutsideContext(UserId)) - return StatusCode(HttpStatusCode.Forbidden); + { + return Forbidden(); + } //tolist requried to handle filtering on computed fields - var result = Repository.AsQueryable().Where(m => m.OrganizationId == key); + var result = Repository.AsQueryable().ByOrganizationId(key); return Ok(result); } - // GET /Organizations(1)/Supplier + /// + /// Henter alle kontrakter for den pågældende leverandør + /// + /// + /// [EnableQuery(MaxExpansionDepth = 3)] [ODataRoute("Organizations({key})/Supplier")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse>))] + [SwaggerResponse(HttpStatusCode.Forbidden)] public IHttpActionResult GetSupplier(int key) { var loggedIntoOrgId = _authService.GetCurrentOrganizationId(UserId); if (loggedIntoOrgId != key && !_authService.HasReadAccessOutsideContext(UserId)) - return StatusCode(HttpStatusCode.Forbidden); + { + return Forbidden(); + } - var result = Repository.AsQueryable().Where(m => m.OrganizationId == key); + var result = Repository.AsQueryable().ByOrganizationId(key); return Ok(result); } // GET /Organizations(1)/ItContracts(1) [EnableQuery] [ODataRoute("Organizations({orgKey})/ItContracts({contractKey})")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse))] + [SwaggerResponse(HttpStatusCode.Forbidden)] + [SwaggerResponse(HttpStatusCode.NotFound)] public IHttpActionResult GetItContracts(int orgKey, int contractKey) { var entity = Repository.AsQueryable().SingleOrDefault(m => m.Id == contractKey); if (entity == null) + { return NotFound(); + } if (_authService.HasReadAccess(UserId, entity)) + { return Ok(entity); + } - return StatusCode(HttpStatusCode.Forbidden); + return Forbidden(); } // TODO refactor this now that we are using MS Sql Server that has support for MARS [EnableQuery(MaxExpansionDepth = 3)] [ODataRoute("Organizations({orgKey})/OrganizationUnits({unitKey})/ItContracts")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse>))] + [SwaggerResponse(HttpStatusCode.Forbidden)] public IHttpActionResult GetItContractsByOrgUnit(int orgKey, int unitKey) { var loggedIntoOrgId = _authService.GetCurrentOrganizationId(UserId); if (loggedIntoOrgId != orgKey && !_authService.HasReadAccessOutsideContext(UserId)) - return StatusCode(HttpStatusCode.Forbidden); + { + return Forbidden(); + } var contracts = new List(); diff --git a/Presentation.Web/Controllers/OData/ItInterfaceExhibitsController.cs b/Presentation.Web/Controllers/OData/ItInterfaceExhibitsController.cs index 8a3aa86cb8..b8ac8ca1b4 100644 --- a/Presentation.Web/Controllers/OData/ItInterfaceExhibitsController.cs +++ b/Presentation.Web/Controllers/OData/ItInterfaceExhibitsController.cs @@ -1,9 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.ItSystem; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; -namespace Presentation.Web.Controllers.OData.OptionControllers +namespace Presentation.Web.Controllers.OData { + [PublicApi] public class ItInterfaceExhibitsController : BaseEntityController { public ItInterfaceExhibitsController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/ItInterfacesController.cs b/Presentation.Web/Controllers/OData/ItInterfacesController.cs index 984e4315de..37b145fe01 100644 --- a/Presentation.Web/Controllers/OData/ItInterfacesController.cs +++ b/Presentation.Web/Controllers/OData/ItInterfacesController.cs @@ -7,9 +7,13 @@ using Core.DomainModel.ItSystem; using Core.DomainServices; using Core.ApplicationServices; +using Presentation.Web.Infrastructure.Attributes; +using Swashbuckle.OData; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.OData { + [PublicApi] public class ItInterfacesController : BaseEntityController { private readonly IAuthenticationService _authService; @@ -22,14 +26,20 @@ public ItInterfacesController(IGenericRepository repository, IAuthe [EnableQuery] [ODataRoute("ItInterfaces")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse))] public override IHttpActionResult Get() { return base.Get(); } - // GET /Organizations(1)/ItInterfaces + /// + /// Henter alle snitflader i organisationen samt offentlige snitflader i andre organisationer + /// + /// + /// [EnableQuery] [ODataRoute("Organizations({key})/ItInterfaces")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse>))] public IHttpActionResult GetItInterfaces(int key) { var result = Repository.AsQueryable().Where(m => m.OrganizationId == key || m.AccessModifier == AccessModifier.Public); @@ -39,6 +49,9 @@ public IHttpActionResult GetItInterfaces(int key) // GET /Organizations(1)/ItInterfaces(1) [EnableQuery] [ODataRoute("Organizations({orgKey})/ItInterfaces({interfaceKey})")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse))] + [SwaggerResponse(HttpStatusCode.Forbidden)] + [SwaggerResponse(HttpStatusCode.NotFound)] public IHttpActionResult GetItInterfaces(int orgKey, int interfaceKey) { var entity = Repository.AsQueryable().SingleOrDefault(m => m.OrganizationId == orgKey && m.Id == interfaceKey); @@ -48,7 +61,7 @@ public IHttpActionResult GetItInterfaces(int orgKey, int interfaceKey) if (_authService.HasReadAccess(UserId, entity)) return Ok(entity); - return StatusCode(HttpStatusCode.Forbidden); + return Forbidden(); } } } diff --git a/Presentation.Web/Controllers/OData/ItProjectRightsController.cs b/Presentation.Web/Controllers/OData/ItProjectRightsController.cs index 5844e47d62..d94710f014 100644 --- a/Presentation.Web/Controllers/OData/ItProjectRightsController.cs +++ b/Presentation.Web/Controllers/OData/ItProjectRightsController.cs @@ -5,12 +5,16 @@ using Core.DomainModel.ItProject; using Core.DomainServices; using Core.ApplicationServices; + using Presentation.Web.Infrastructure.Attributes; + using Swashbuckle.OData; + using Swashbuckle.Swagger.Annotations; -namespace Presentation.Web.Controllers.OData + namespace Presentation.Web.Controllers.OData { using System; using System.Net; + [PublicApi] public class ItProjectRightsController : BaseEntityController { private IAuthenticationService _authService; @@ -23,6 +27,7 @@ public ItProjectRightsController(IGenericRepository repository, // GET /Organizations(1)/ItProjects(1)/Rights [EnableQuery] [ODataRoute("Organizations({orgId})/ItProjects({projId})/Rights")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse>))] public IHttpActionResult GetByItProject(int orgId, int projId) { // TODO figure out how to check auth @@ -33,6 +38,7 @@ public IHttpActionResult GetByItProject(int orgId, int projId) // GET /Users(1)/ItProjectRights [EnableQuery] [ODataRoute("Users({userId})/ItProjectRights")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse>))] public IHttpActionResult GetByUser(int userId) { // TODO figure out how to check auth @@ -48,7 +54,9 @@ public override IHttpActionResult Delete(int key) return NotFound(); if (!_authService.HasWriteAccess(UserId, entity) && !_authService.IsLocalAdmin(this.UserId)) - return Unauthorized(); + { + return Forbidden(); + } try { @@ -69,22 +77,21 @@ public override IHttpActionResult Patch(int key, Delta delta) // does the entity exist? if (entity == null) + { return NotFound(); + } // check if user is allowed to write to the entity if (!_authService.HasWriteAccess(UserId, entity) && !_authService.IsLocalAdmin(this.UserId)) - return StatusCode(HttpStatusCode.Forbidden); - - //Check if user is allowed to set accessmodifier to public - //var accessModifier = (entity as IHasAccessModifier)?.AccessModifier; - //if (accessModifier == AccessModifier.Public && !_authService.CanExecute(UserId, Feature.CanSetAccessModifierToPublic)) - //{ - // return Unauthorized(); - //} + { + return Forbidden(); + } // check model state if (!ModelState.IsValid) + { return BadRequest(ModelState); + } try { diff --git a/Presentation.Web/Controllers/OData/ItProjectStatusUpdatesController.cs b/Presentation.Web/Controllers/OData/ItProjectStatusUpdatesController.cs index 4738a6f2c1..790ada93b5 100644 --- a/Presentation.Web/Controllers/OData/ItProjectStatusUpdatesController.cs +++ b/Presentation.Web/Controllers/OData/ItProjectStatusUpdatesController.cs @@ -1,9 +1,11 @@ using Core.DomainModel.ItProject; using Core.DomainServices; using Core.ApplicationServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData { + [PublicApi] public class ItProjectStatusUpdatesController : BaseEntityController { public ItProjectStatusUpdatesController(IGenericRepository diff --git a/Presentation.Web/Controllers/OData/ItProjectsController.cs b/Presentation.Web/Controllers/OData/ItProjectsController.cs index c869c9bc4d..190e64bf03 100644 --- a/Presentation.Web/Controllers/OData/ItProjectsController.cs +++ b/Presentation.Web/Controllers/OData/ItProjectsController.cs @@ -1,19 +1,24 @@ using System.Collections.Generic; using System.Data.Entity; using System.Linq; +using System.Net; using System.Web.Http; using System.Web.OData; using System.Web.OData.Routing; using Core.DomainModel; using Core.DomainModel.ItProject; using Core.DomainServices; -using System.Net; using Core.DomainModel.Organization; using Core.ApplicationServices; +using Core.DomainServices.Extensions; +using Presentation.Web.Infrastructure.Attributes; +using Swashbuckle.OData; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.OData { [Authorize] + [PublicApi] public class ItProjectsController : BaseEntityController { private readonly IGenericRepository _orgUnitRepository; @@ -28,34 +33,37 @@ public ItProjectsController(IGenericRepository repository, IGenericRe [EnableQuery] [ODataRoute("ItProjects")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse))] public override IHttpActionResult Get() { return base.Get(); - - //if (AuthenticationService.HasReadAccessOutsideContext(UserId)) - // return base.Get(); - - //var orgId = CurrentOrganizationId; - //return Ok(Repository.AsQueryable().Where(x => x.OrganizationId == orgId)); } - // GET /Organizations(1)/ItProjects + /// + /// Henter organisationens projekter samt offentlige projekter fra andre organisationer + /// + /// + /// [EnableQuery] [ODataRoute("Organizations({key})/ItProjects")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse>))] + [SwaggerResponse(HttpStatusCode.Forbidden)] public IHttpActionResult GetItProjects(int key) { var loggedIntoOrgId = _authService.GetCurrentOrganizationId(UserId); if (!_authService.HasReadAccessOutsideContext(UserId)) { if (loggedIntoOrgId != key) - return StatusCode(HttpStatusCode.Forbidden); + { + return Forbidden(); + } - var result = Repository.AsQueryable().Where(m => m.OrganizationId == key); + var result = Repository.AsQueryable().ByOrganizationId(key); return Ok(result); } else { - var result = Repository.AsQueryable().Where(m => m.OrganizationId == key || m.AccessModifier == AccessModifier.Public); + var result = Repository.AsQueryable().ByPublicAccessOrOrganizationId(key); return Ok(result); } } @@ -63,27 +71,38 @@ public IHttpActionResult GetItProjects(int key) // GET /Organizations(1)/ItProjects(1) [EnableQuery] [ODataRoute("Organizations({orgKey})/ItProjects({projKey})")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse))] + [SwaggerResponse(HttpStatusCode.Forbidden)] + [SwaggerResponse(HttpStatusCode.NotFound)] public IHttpActionResult GetItProjects(int orgKey, int projKey) { var entity = Repository.AsQueryable().SingleOrDefault(m => m.Id == projKey); if (entity == null) + { return NotFound(); + } if (_authService.HasReadAccess(UserId, entity)) + { return Ok(entity); + } - return StatusCode(HttpStatusCode.Forbidden); + return Forbidden(); } // TODO for now only read actions are allowed, in future write will be enabled - but keep security in mind! // GET /Organizations(1)/OrganizationUnits(1)/ItProjects [EnableQuery] [ODataRoute("Organizations({orgKey})/OrganizationUnits({unitKey})/ItProjects")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse>))] + [SwaggerResponse(HttpStatusCode.Forbidden)] public IHttpActionResult GetItProjectsByOrgUnit(int orgKey, int unitKey) { var loggedIntoOrgId = _authService.GetCurrentOrganizationId(UserId); if (loggedIntoOrgId != orgKey && !_authService.HasReadAccessOutsideContext(UserId)) - return StatusCode(HttpStatusCode.Forbidden); + { + return Forbidden(); + } var projects = new List(); diff --git a/Presentation.Web/Controllers/OData/ItSystemRightsController.cs b/Presentation.Web/Controllers/OData/ItSystemRightsController.cs index 05bdacfb7b..21c3af5b43 100644 --- a/Presentation.Web/Controllers/OData/ItSystemRightsController.cs +++ b/Presentation.Web/Controllers/OData/ItSystemRightsController.cs @@ -1,67 +1,79 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using System.Web.Http; using System.Web.OData; using System.Web.OData.Routing; using Core.DomainModel.ItSystem; using Core.DomainServices; using Core.ApplicationServices; +using Core.ApplicationServices.Authorization; +using Presentation.Web.Infrastructure.Attributes; +using Swashbuckle.OData; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.OData { using System; using System.Net; + [PublicApi] public class ItSystemRightsController : BaseEntityController { - private IAuthenticationService _authService; - public ItSystemRightsController(IGenericRepository repository, IAuthenticationService authService) - : base(repository, authService) + public ItSystemRightsController( + IGenericRepository repository, + IAuthenticationService authService, + IAuthorizationContext authorizationContext) + : base(repository, authService, authorizationContext) { - this._authService = authService; } - // GET /Organizations(1)/ItSystemUsages + // GET /Organizations(1)/ItSystemUsages(1)/Rights [EnableQuery] [ODataRoute("Organizations({orgId})/ItSystemUsages({usageId})/Rights")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse>))] public IHttpActionResult GetByItSystem(int orgId, int usageId) { - // TODO figure out how to check auth - var result = Repository.AsQueryable().Where(x => x.Object.OrganizationId == orgId && x.ObjectId == usageId); - return Ok(result); + var result = Repository.AsQueryable().Where(x => x.Object.OrganizationId == orgId && x.ObjectId == usageId).ToList(); + + result = FilterByAccessControl(result); + + return Ok(result.AsQueryable()); } // GET /Users(1)/ItProjectRights [EnableQuery] [ODataRoute("Users({userId})/ItSystemRights")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse>))] public IHttpActionResult GetByUser(int userId) { - // TODO figure out how to check auth - var result = Repository.AsQueryable().Where(x => x.UserId == userId); - return Ok(result); + var result = Repository.AsQueryable().Where(x => x.UserId == userId).ToList(); + + result = FilterByAccessControl(result); + + return Ok(result.AsQueryable()); } public override IHttpActionResult Patch(int key, Delta delta) { var entity = Repository.GetByKey(key); + // check model state + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + // does the entity exist? if (entity == null) + { return NotFound(); + } // check if user is allowed to write to the entity - if (!_authService.HasWriteAccess(UserId, entity) && !_authService.IsLocalAdmin(this.UserId)) - return StatusCode(HttpStatusCode.Forbidden); - - //Check if user is allowed to set accessmodifier to public - //var accessModifier = (entity as IHasAccessModifier)?.AccessModifier; - //if (accessModifier == AccessModifier.Public && !_authService.CanExecute(UserId, Feature.CanSetAccessModifierToPublic)) - //{ - // return Unauthorized(); - //} - - // check model state - if (!ModelState.IsValid) - return BadRequest(ModelState); + if (AllowWrite(entity) == false) + { + return Forbidden(); + } try { @@ -85,10 +97,14 @@ public override IHttpActionResult Delete(int key) var entity = Repository.GetByKey(key); if (entity == null) + { return NotFound(); + } - if (!_authService.HasWriteAccess(UserId, entity) && !_authService.IsLocalAdmin(this.UserId)) - return Unauthorized(); + if (AllowWrite(entity) == false) + { + return Forbidden(); + } try { @@ -102,5 +118,11 @@ public override IHttpActionResult Delete(int key) return StatusCode(HttpStatusCode.NoContent); } + + private List FilterByAccessControl(List result) + { + result = result.Where(AllowRead).ToList(); + return result; + } } } diff --git a/Presentation.Web/Controllers/OData/ItSystemUsagesController.cs b/Presentation.Web/Controllers/OData/ItSystemUsagesController.cs index 4a7ad8c147..84fb2ee700 100644 --- a/Presentation.Web/Controllers/OData/ItSystemUsagesController.cs +++ b/Presentation.Web/Controllers/OData/ItSystemUsagesController.cs @@ -5,56 +5,77 @@ using System.Web.Http; using System.Web.OData; using System.Web.OData.Routing; +using System.Net; using Core.DomainModel.ItSystemUsage; using Core.DomainServices; -using System.Net; using Core.DomainModel.Organization; -using Core.ApplicationServices; using Core.DomainModel.ItSystem; +using Core.ApplicationServices; +using Core.ApplicationServices.Authorization; +using Core.DomainServices.Authorization; +using Core.DomainServices.Extensions; +using Presentation.Web.Infrastructure.Attributes; +using Swashbuckle.OData; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.OData { + [PublicApi] public class ItSystemUsagesController : BaseEntityController { private readonly IGenericRepository _orgUnitRepository; private readonly IGenericRepository _accessTypeRepository; - private readonly IAuthenticationService _authService; - public ItSystemUsagesController(IGenericRepository repository, IGenericRepository orgUnitRepository, IAuthenticationService authService, IGenericRepository accessTypeRepository ) - : base(repository, authService) + public ItSystemUsagesController(IGenericRepository repository, IGenericRepository orgUnitRepository, + IAuthenticationService authService, IGenericRepository accessTypeRepository, IAuthorizationContext authorizationContext) + : base(repository, authService, authorizationContext) { _orgUnitRepository = orgUnitRepository; _accessTypeRepository = accessTypeRepository; - _authService = authService; } - // GET /Organizations(1)/ItSystemUsages - [EnableQuery(MaxExpansionDepth = 4)] // MaxExpansionDepth is 3 because we need to do MainContract($expand=ItContract($expand=Supplier)) - [ODataRoute("Organizations({key})/ItSystemUsages")] - public IHttpActionResult GetItSystems(int key) + /// + /// Henter alle organisationens IT-Systemanvendelser. + /// + /// + /// + [EnableQuery(MaxExpansionDepth = 4)] // MaxExpansionDepth is 4 because we need to do MainContract($expand=ItContract($expand=Supplier)) + [ODataRoute("Organizations({orgKey})/ItSystemUsages")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse>))] + [SwaggerResponse(HttpStatusCode.Forbidden)] + public IHttpActionResult GetItSystems(int orgKey) { - var loggedIntoOrgId = _authService.GetCurrentOrganizationId(UserId); - if (loggedIntoOrgId != key && !_authService.HasReadAccessOutsideContext(UserId)) - return StatusCode(HttpStatusCode.Forbidden); - //Tolist() is required for filtering on computed values in odata. - var result = Repository.AsQueryable().Where(m => m.OrganizationId == key); + //Usages are local so full access is required + var accessLevel = GetOrganizationReadAccessLevel(orgKey); + if (accessLevel != OrganizationDataReadAccessLevel.All) + { + return Forbidden(); + } + + var result = Repository.AsQueryable().ByOrganizationId(orgKey, accessLevel); + return Ok(result); } - // TODO refactor this now that we are using MS Sql Server that has support for MARS - [EnableQuery(MaxExpansionDepth = 4)] // MaxExpansionDepth is 3 because we need to do MainContract($expand=ItContract($expand=Supplier)) + /// + /// Henter alle IT-Systemanvendelser for den pågældende organisationsenhed + /// + /// + /// + /// + [EnableQuery(MaxExpansionDepth = 4)] // MaxExpansionDepth is 4 because we need to do MainContract($expand=ItContract($expand=Supplier)) [ODataRoute("Organizations({orgKey})/OrganizationUnits({unitKey})/ItSystemUsages")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse>))] + [SwaggerResponse(HttpStatusCode.Forbidden)] public IHttpActionResult GetItSystemsByOrgUnit(int orgKey, int unitKey) { - var loggedIntoOrgId = _authService.GetCurrentOrganizationId(UserId); - if (loggedIntoOrgId != orgKey && !_authService.HasReadAccessOutsideContext(UserId)) - return StatusCode(HttpStatusCode.Forbidden); + //Usages are local so full access is required + if (GetOrganizationReadAccessLevel(orgKey) != OrganizationDataReadAccessLevel.All) + { + return Forbidden(); + } var systemUsages = new List(); - - // using iteration instead of recursion else we're running into - // an "multiple DataReaders open" issue and MySQL doesn't support MARS - var queue = new Queue(); queue.Enqueue(unitKey); while (queue.Count > 0) @@ -64,11 +85,9 @@ public IHttpActionResult GetItSystemsByOrgUnit(int orgKey, int unitKey) .Include(x => x.Children) .Include(x => x.Using.Select(y => y.ResponsibleItSystemUsage)) .First(x => x.OrganizationId == orgKey && x.Id == orgUnitKey); - var responsible = orgUnit.Using.Select(x => x.ResponsibleItSystemUsage).Where(x => x != null).ToList(); systemUsages.AddRange(responsible); - var childIds = orgUnit.Children.Select(x => x.Id); foreach (var childId in childIds) { @@ -80,13 +99,19 @@ public IHttpActionResult GetItSystemsByOrgUnit(int orgKey, int unitKey) } [AcceptVerbs("POST", "PUT")] - public IHttpActionResult CreateRef([FromODataUri] int key, string navigationProperty, [FromBody] Uri link) + public IHttpActionResult CreateRef([FromODataUri] int systemUsageKey, string navigationProperty, [FromBody] Uri link) { - var itSystemUsage = Repository.GetByKey(key); + var itSystemUsage = Repository.GetByKey(systemUsageKey); if (itSystemUsage == null) { return NotFound(); } + + if (!AllowWrite(itSystemUsage)) + { + return Forbidden(); + } + switch (navigationProperty) { case "AccessTypes": @@ -105,6 +130,7 @@ public IHttpActionResult CreateRef([FromODataUri] int key, string navigationProp } Repository.Save(); + return StatusCode(HttpStatusCode.NoContent); } @@ -113,7 +139,12 @@ public IHttpActionResult DeleteRef([FromODataUri] int key, [FromODataUri] string var itSystemUsage = Repository.GetByKey(key); if (itSystemUsage == null) { - return StatusCode(HttpStatusCode.NotFound); + return NotFound(); + } + + if (!AllowWrite(itSystemUsage)) + { + return Forbidden(); } switch (navigationProperty) @@ -132,6 +163,7 @@ public IHttpActionResult DeleteRef([FromODataUri] int key, [FromODataUri] string return StatusCode(HttpStatusCode.NotImplemented); } + Repository.Save(); return StatusCode(HttpStatusCode.NoContent); diff --git a/Presentation.Web/Controllers/OData/ItSystemsController.cs b/Presentation.Web/Controllers/OData/ItSystemsController.cs index 532ef76c46..4b0ecf8fb6 100644 --- a/Presentation.Web/Controllers/OData/ItSystemsController.cs +++ b/Presentation.Web/Controllers/OData/ItSystemsController.cs @@ -1,92 +1,80 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; +using System.Net; using System.Web.Http; using System.Web.OData; using System.Web.OData.Routing; -using Core.DomainModel; using Core.DomainModel.ItSystem; using Core.DomainServices; -using System.Net; using Core.ApplicationServices; +using Core.ApplicationServices.Authorization; +using Core.DomainServices.Authorization; +using Core.DomainServices.Extensions; +using Presentation.Web.Infrastructure.Attributes; +using Swashbuckle.OData; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.OData { + [PublicApi] public class ItSystemsController : BaseEntityController { - private readonly IAuthenticationService _authService; - - public ItSystemsController(IGenericRepository repository, IAuthenticationService authService) - : base(repository, authService) - { - _authService = authService; - } - - [ODataRoute("ItSystems")] - public override IHttpActionResult Get() + public ItSystemsController(IGenericRepository repository, IAuthenticationService authService, IAuthorizationContext authorizationContext) + : base(repository, authService, authorizationContext) { - var test = base.Get(); - return base.Get(); - //if (AuthenticationService.HasReadAccessOutsideContext(CurentUser)) - // return base.Get(); - - //var orgId = CurrentOrganizationId; - //return Ok(Repository.AsQueryable().Where(x => x.OrganizationId == orgId)); } - // GET /Organizations(1)/ItSystems + /// + /// Henter alle organisationens IT-Systemer samt offentlige IT-systemer fra andre organisationer. + /// Resultatet filtreres i hht. brugerens læserettigheder i den opgældende organisation, samt på tværs af organisationer. + /// + /// + /// [EnableQuery] - [ODataRoute("Organizations({key})/ItSystems")] - public IHttpActionResult GetItSystems(int key) + [ODataRoute("Organizations({orgKey})/ItSystems")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse>))] + [SwaggerResponse(HttpStatusCode.Forbidden)] + public IHttpActionResult GetItSystems(int orgKey) { - var loggedIntoOrgId = _authService.GetCurrentOrganizationId(UserId); - if (!_authService.HasReadAccessOutsideContext(UserId)) + var readAccessLevel = GetOrganizationReadAccessLevel(orgKey); + if (readAccessLevel == OrganizationDataReadAccessLevel.None) { - if (loggedIntoOrgId != key) - return StatusCode(HttpStatusCode.Forbidden); - - var result = Repository.AsQueryable().Where(m => m.OrganizationId == key); - return Ok(result); + return Forbidden(); } - else - { - var result = Repository.AsQueryable().Where(m => m.OrganizationId == key || m.AccessModifier == AccessModifier.Public); - return Ok(result); - } - } - // GET /Organizations(1)/BelongingSystems - [EnableQuery] - [ODataRoute("Organizations({key})/BelongingSystems")] - public IHttpActionResult GetBelongingSystems(int key) - { - var loggedIntoOrgId = _authService.GetCurrentOrganizationId(UserId); - if (!_authService.HasReadAccessOutsideContext(UserId)) - { - if (loggedIntoOrgId != key) - return StatusCode(HttpStatusCode.Forbidden); + var result = Repository + .AsQueryable() + .ByOrganizationDataAndPublicDataFromOtherOrganizations(orgKey, readAccessLevel, GetCrossOrganizationReadAccessLevel()); - var result = Repository.AsQueryable().Where(m => m.BelongsToId == key); - return Ok(result); - } - else - { - var result = Repository.AsQueryable().Where(m => m.OrganizationId == key || m.AccessModifier == AccessModifier.Public); - return Ok(result); - } + return Ok(result); } // GET /Organizations(1)/ItSystems(1) [EnableQuery] [ODataRoute("Organizations({orgKey})/ItSystems({sysKey})")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse))] + [SwaggerResponse(HttpStatusCode.Forbidden)] + [SwaggerResponse(HttpStatusCode.NotFound)] public IHttpActionResult GetItSystems(int orgKey, int sysKey) { - var entity = Repository.AsQueryable().SingleOrDefault(m => m.Id == sysKey); - if (entity == null) + var system = Repository.GetByKey(sysKey); + if (!AllowRead(system)) + { + return Forbidden(); + } + if (system == null) + { return NotFound(); + } - if (_authService.HasReadAccess(UserId, entity)) - return Ok(entity); + return Ok(system); + } - return StatusCode(HttpStatusCode.Forbidden); + [ODataRoute("ItSystems")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse>))] + public override IHttpActionResult Get() + { + return base.Get(); } } } diff --git a/Presentation.Web/Controllers/OData/LocalOptionBaseController.cs b/Presentation.Web/Controllers/OData/LocalOptionBaseController.cs index 8d475f22be..79cf672d2b 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionBaseController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionBaseController.cs @@ -4,13 +4,14 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net; using System.Web.Http; using System.Web.OData; +using Presentation.Web.Infrastructure.Attributes; using static System.String; namespace Presentation.Web.Controllers.OData { + [PublicApi] public class LocalOptionBaseController : BaseEntityController where TLocalModelType : LocalOptionEntity, new() where TOptionType : OptionEntity { private readonly IAuthenticationService _authService; @@ -26,9 +27,6 @@ public LocalOptionBaseController(IGenericRepository repository, [EnableQuery] public override IHttpActionResult Get() { - if (UserId == 0) - return Unauthorized(); - var orgId = _authService.GetCurrentOrganizationId(UserId); var localOptionsResult = Repository.AsQueryable().Where(x => x.OrganizationId == orgId).ToList(); var globalOptionsResult = _optionsRepository.AsQueryable().ToList(); @@ -60,9 +58,6 @@ public override IHttpActionResult Get() [EnableQuery] public override IHttpActionResult Get(int key) { - if (UserId == 0) - return Unauthorized(); - var orgId = _authService.GetCurrentOrganizationId(UserId); var globalOptionResult = _optionsRepository.AsQueryable().Where(x => x.Id == key); @@ -93,13 +88,15 @@ public override IHttpActionResult Get(int key) public override IHttpActionResult Post(TLocalModelType entity) { if (!ModelState.IsValid) + { return BadRequest(ModelState); + } entity.OrganizationId = _authService.GetCurrentOrganizationId(UserId); if (!_authService.HasWriteAccess(UserId, entity)) { - return Unauthorized(); + return Forbidden(); } var orgId = _authService.GetCurrentOrganizationId(UserId); @@ -152,15 +149,21 @@ public override IHttpActionResult Patch(int key, Delta delta) var localOption = localOptionSearch.First(); // does the entity exist? if (localOption == null) + { return NotFound(); + } // check if user is allowed to write to the entity if (!_authService.HasWriteAccess(UserId, localOption)) - return StatusCode(HttpStatusCode.Forbidden); + { + return Forbidden(); + } // check model state if (!ModelState.IsValid) + { return BadRequest(ModelState); + } try { @@ -207,7 +210,9 @@ public override IHttpActionResult Delete(int key) return NotFound(); if (!_authService.HasWriteAccess(UserId, localOption)) - return Unauthorized(); + { + return Forbidden(); + } try { diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalAgreementElementTypesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalAgreementElementTypesController.cs index 69596832fd..7080baf285 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalAgreementElementTypesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalAgreementElementTypesController.cs @@ -2,9 +2,11 @@ using Core.DomainModel.ItContract; using Core.DomainModel.LocalOptions; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { + [InternalApi] public class LocalAgreementElementTypesController : LocalOptionBaseController { public LocalAgreementElementTypesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalArchiveLocationsController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalArchiveLocationsController.cs index e444d3705b..b708d70405 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalArchiveLocationsController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalArchiveLocationsController.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { @@ -11,6 +8,7 @@ namespace Presentation.Web.Controllers.OData.LocalOptionControllers using Core.DomainModel.LocalOptions; using Core.DomainServices; + [InternalApi] public class LocalArchiveLocationsController : LocalOptionBaseController { public LocalArchiveLocationsController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalArchiveTestLocationsController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalArchiveTestLocationsController.cs index 9a3d24c81e..8e6ff40a0e 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalArchiveTestLocationsController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalArchiveTestLocationsController.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { @@ -11,6 +8,7 @@ namespace Presentation.Web.Controllers.OData.LocalOptionControllers using Core.DomainModel.LocalOptions; using Core.DomainServices; + [InternalApi] public class LocalArchiveTestLocationsController : LocalOptionBaseController { public LocalArchiveTestLocationsController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalArchiveTypesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalArchiveTypesController.cs index a2b9e97099..5be7cd89cc 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalArchiveTypesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalArchiveTypesController.cs @@ -3,9 +3,11 @@ using Core.DomainModel.ItSystemUsage; using Core.DomainModel.LocalOptions; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { + [InternalApi] public class LocalArchiveTypesController : LocalOptionBaseController { public LocalArchiveTypesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalBusinessTypesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalBusinessTypesController.cs index 2da379f2c8..131a5455b5 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalBusinessTypesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalBusinessTypesController.cs @@ -2,12 +2,11 @@ using Core.DomainModel.ItSystem; using Core.DomainModel.LocalOptions; using Core.DomainServices; -using System.Web.Http; -using System.Web.OData; -using System.Web.OData.Routing; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { + [InternalApi] public class LocalBusinessTypesController : LocalOptionBaseController { public LocalBusinessTypesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalDataTypesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalDataTypesController.cs index 7fea57b0ed..a9b1ec5596 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalDataTypesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalDataTypesController.cs @@ -2,9 +2,11 @@ using Core.DomainModel.ItSystem; using Core.DomainModel.LocalOptions; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { + [InternalApi] public class LocalDataTypesController : LocalOptionBaseController { public LocalDataTypesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalFrequencyTypesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalFrequencyTypesController.cs index e6f6c8ff59..f32fb32e7d 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalFrequencyTypesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalFrequencyTypesController.cs @@ -3,9 +3,11 @@ using Core.DomainModel.ItSystemUsage; using Core.DomainModel.LocalOptions; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { + [InternalApi] public class LocalFrequencyTypesController : LocalOptionBaseController { public LocalFrequencyTypesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalGoalTypesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalGoalTypesController.cs index 22421d08f4..b393e6461f 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalGoalTypesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalGoalTypesController.cs @@ -2,9 +2,11 @@ using Core.DomainModel.ItProject; using Core.DomainModel.LocalOptions; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { + [InternalApi] public class LocalGoalTypesController : LocalOptionBaseController { public LocalGoalTypesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalHandoverTrialTypesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalHandoverTrialTypesController.cs index 2b09f5c305..f40e4a47e1 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalHandoverTrialTypesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalHandoverTrialTypesController.cs @@ -2,9 +2,11 @@ using Core.DomainModel.ItContract; using Core.DomainModel.LocalOptions; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { + [InternalApi] public class LocalHandoverTrialTypesController : LocalOptionBaseController { public LocalHandoverTrialTypesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalInterfaceTypesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalInterfaceTypesController.cs index 96d9ac7b16..2b7dcec9e3 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalInterfaceTypesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalInterfaceTypesController.cs @@ -2,9 +2,11 @@ using Core.DomainModel.ItSystem; using Core.DomainModel.LocalOptions; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { + [InternalApi] public class LocalInterfaceTypesController : LocalOptionBaseController { public LocalInterfaceTypesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItContractRolesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItContractRolesController.cs index 88b4507ae5..fdda40ab58 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItContractRolesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItContractRolesController.cs @@ -2,9 +2,11 @@ using Core.DomainModel.ItContract; using Core.DomainModel.LocalOptions; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { + [InternalApi] public class LocalItContractRolesController : LocalOptionBaseController { public LocalItContractRolesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItContractTemplateTypesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItContractTemplateTypesController.cs index 3df651aeb8..7b13b6b6fb 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItContractTemplateTypesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItContractTemplateTypesController.cs @@ -2,9 +2,11 @@ using Core.DomainModel.ItContract; using Core.DomainModel.LocalOptions; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { + [InternalApi] public class LocalItContractTemplateTypesController : LocalOptionBaseController { public LocalItContractTemplateTypesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItContractTypesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItContractTypesController.cs index 9f53ef4d9a..11b7732593 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItContractTypesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItContractTypesController.cs @@ -2,9 +2,11 @@ using Core.DomainModel.ItContract; using Core.DomainModel.LocalOptions; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { + [InternalApi] public class LocalItContractTypesController : LocalOptionBaseController { public LocalItContractTypesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItInterfaceTypesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItInterfaceTypesController.cs index bb22ca21b7..26167e126f 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItInterfaceTypesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItInterfaceTypesController.cs @@ -2,9 +2,11 @@ using Core.DomainModel.ItSystem; using Core.DomainModel.LocalOptions; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { + [InternalApi] public class LocalItInterfaceTypesController : LocalOptionBaseController { public LocalItInterfaceTypesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItProjectRolesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItProjectRolesController.cs index 17bb4339e5..6cb2752474 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItProjectRolesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItProjectRolesController.cs @@ -2,9 +2,11 @@ using Core.DomainModel.ItProject; using Core.DomainModel.LocalOptions; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { + [InternalApi] public class LocalItProjectRolesController : LocalOptionBaseController { public LocalItProjectRolesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItProjectTypesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItProjectTypesController.cs index 01215f4e3d..313a701f83 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItProjectTypesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItProjectTypesController.cs @@ -2,9 +2,11 @@ using Core.DomainModel.ItProject; using Core.DomainModel.LocalOptions; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { + [InternalApi] public class LocalItProjectTypesController : LocalOptionBaseController { public LocalItProjectTypesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItSystemCategoriesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItSystemCategoriesController.cs index 3badd62f83..76fa424e43 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItSystemCategoriesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItSystemCategoriesController.cs @@ -1,16 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using Core.ApplicationServices; +using Core.ApplicationServices; using Core.DomainModel.ItSystem; using Core.DomainModel.ItSystemUsage; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { using Core.DomainModel.LocalOptions; - + [InternalApi] public class LocalItSystemCategoriesController : LocalOptionBaseController { public LocalItSystemCategoriesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItSystemRolesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItSystemRolesController.cs index 7374c9db44..ecf13bf88c 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItSystemRolesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItSystemRolesController.cs @@ -2,9 +2,11 @@ using Core.DomainModel.ItSystem; using Core.DomainModel.LocalOptions; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { + [InternalApi] public class LocalItSystemRolesController : LocalOptionBaseController { public LocalItSystemRolesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItSystemTypesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItSystemTypesController.cs index 97bc224856..a90fc3a4dc 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItSystemTypesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalItSystemTypesController.cs @@ -2,9 +2,11 @@ using Core.DomainModel.ItSystem; using Core.DomainModel.LocalOptions; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { + [InternalApi] public class LocalItSystemTypesController : LocalOptionBaseController { public LocalItSystemTypesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalMethodTypesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalMethodTypesController.cs index b09ae0bb94..32cdb58a6c 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalMethodTypesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalMethodTypesController.cs @@ -2,9 +2,11 @@ using Core.DomainModel.ItSystem; using Core.DomainModel.LocalOptions; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { + [InternalApi] public class LocalMethodTypesController : LocalOptionBaseController { public LocalMethodTypesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalOptionExtendTypesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalOptionExtendTypesController.cs index 980ba6a6da..7a6e19b056 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalOptionExtendTypesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalOptionExtendTypesController.cs @@ -2,9 +2,11 @@ using Core.DomainModel.ItContract; using Core.DomainModel.LocalOptions; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { + [InternalApi] public class LocalOptionExtendTypesController : LocalOptionBaseController { public LocalOptionExtendTypesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalOrganizationUnitRolesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalOrganizationUnitRolesController.cs index e0683c137e..805220d39a 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalOrganizationUnitRolesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalOrganizationUnitRolesController.cs @@ -2,10 +2,11 @@ using Core.DomainModel.LocalOptions; using Core.DomainModel.Organization; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { - + [InternalApi] public class LocalOrganizationUnitRolesController : LocalOptionBaseController { public LocalOrganizationUnitRolesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalPaymentFrequencyTypesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalPaymentFrequencyTypesController.cs index b5ef3d5102..d7e2a41802 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalPaymentFrequencyTypesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalPaymentFrequencyTypesController.cs @@ -2,9 +2,11 @@ using Core.DomainModel.ItContract; using Core.DomainModel.LocalOptions; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { + [InternalApi] public class LocalPaymentFrequencyTypesController : LocalOptionBaseController { public LocalPaymentFrequencyTypesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalPaymentModelTypesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalPaymentModelTypesController.cs index 5cc965f35b..39615ea2ca 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalPaymentModelTypesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalPaymentModelTypesController.cs @@ -2,9 +2,11 @@ using Core.DomainModel.ItContract; using Core.DomainModel.LocalOptions; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { + [InternalApi] public class LocalPaymentModelTypesController : LocalOptionBaseController { public LocalPaymentModelTypesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalPriceRegulationTypesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalPriceRegulationTypesController.cs index 7df50b6a55..3746b6d994 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalPriceRegulationTypesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalPriceRegulationTypesController.cs @@ -2,9 +2,11 @@ using Core.DomainModel.ItContract; using Core.DomainModel.LocalOptions; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { + [InternalApi] public class LocalPriceRegulationTypesController : LocalOptionBaseController { public LocalPriceRegulationTypesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalProcurementStrategyTypesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalProcurementStrategyTypesController.cs index ec2b6e85fe..2a7b1ae197 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalProcurementStrategyTypesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalProcurementStrategyTypesController.cs @@ -2,9 +2,11 @@ using Core.DomainModel.ItContract; using Core.DomainModel.LocalOptions; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { + [InternalApi] public class LocalProcurementStrategyTypesController : LocalOptionBaseController { public LocalProcurementStrategyTypesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalPurchaseFormTypesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalPurchaseFormTypesController.cs index a587636c47..b3e1fcf0bf 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalPurchaseFormTypesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalPurchaseFormTypesController.cs @@ -2,9 +2,11 @@ using Core.DomainModel.ItContract; using Core.DomainModel.LocalOptions; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { + [InternalApi] public class LocalPurchaseFormTypesController : LocalOptionBaseController { public LocalPurchaseFormTypesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalRegisterTypesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalRegisterTypesController.cs index b888036cf0..be147a16a4 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalRegisterTypesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalRegisterTypesController.cs @@ -2,9 +2,11 @@ using Core.DomainModel.ItSystem; using Core.DomainModel.ItSystemUsage; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { + [InternalApi] public class LocalRegisterTypesController : LocalOptionBaseController { public LocalRegisterTypesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalRegularPersonalDataTypesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalRegularPersonalDataTypesController.cs index 883d13de39..cfa6c45b5c 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalRegularPersonalDataTypesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalRegularPersonalDataTypesController.cs @@ -1,10 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.ItSystem; -using Core.DomainModel.ItSystemUsage; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class LocalRegularPersonalDataTypesController : LocalOptionBaseController { public LocalRegularPersonalDataTypesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalReportCategoryTypesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalReportCategoryTypesController.cs index 309823c869..9f465dd0fa 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalReportCategoryTypesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalReportCategoryTypesController.cs @@ -2,9 +2,11 @@ using Core.DomainModel.LocalOptions; using Core.DomainModel.Reports; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { + [InternalApi] public class LocalReportCategoryTypesController : LocalOptionBaseController { public LocalReportCategoryTypesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalSensistivePersonalDataTypesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalSensistivePersonalDataTypesController.cs index abdff90bd5..593915c09d 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalSensistivePersonalDataTypesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalSensistivePersonalDataTypesController.cs @@ -1,10 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.ItSystem; -using Core.DomainModel.ItSystemUsage; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class LocalSensistivePersonalDataTypesController : LocalOptionBaseController { public LocalSensistivePersonalDataTypesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalSensitiveDataTypesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalSensitiveDataTypesController.cs index 6487789689..6cdabfa22c 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalSensitiveDataTypesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalSensitiveDataTypesController.cs @@ -3,9 +3,11 @@ using Core.DomainModel.ItSystemUsage; using Core.DomainModel.LocalOptions; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { + [InternalApi] public class LocalSensitiveDataTypesController : LocalOptionBaseController { public LocalSensitiveDataTypesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalTerminationDeadlineTypesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalTerminationDeadlineTypesController.cs index 4c6665e18f..d997e41ccb 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalTerminationDeadlineTypesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalTerminationDeadlineTypesController.cs @@ -2,9 +2,11 @@ using Core.DomainModel.ItContract; using Core.DomainModel.LocalOptions; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { + [InternalApi] public class LocalTerminationDeadlineTypesController : LocalOptionBaseController { public LocalTerminationDeadlineTypesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalTsaTypesController.cs b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalTsaTypesController.cs index a8013dc9bc..16913551d4 100644 --- a/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalTsaTypesController.cs +++ b/Presentation.Web/Controllers/OData/LocalOptionControllers/LocalTsaTypesController.cs @@ -2,9 +2,11 @@ using Core.DomainModel.ItSystem; using Core.DomainModel.LocalOptions; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.LocalOptionControllers { + [InternalApi] public class LocalTsaTypesController : LocalOptionBaseController { public LocalTsaTypesController(IGenericRepository repository, IAuthenticationService authService, IGenericRepository optionsRepository) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/AgreementElementTypesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/AgreementElementTypesController.cs index 199837972c..16ce6d67c8 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/AgreementElementTypesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/AgreementElementTypesController.cs @@ -1,9 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.ItContract; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class AgreementElementTypesController: BaseOptionController { public AgreementElementTypesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/ArchiveLocationsController.cs b/Presentation.Web/Controllers/OData/OptionControllers/ArchiveLocationsController.cs index 69e722b2ae..3670cfea11 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/ArchiveLocationsController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/ArchiveLocationsController.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { @@ -10,6 +7,7 @@ namespace Presentation.Web.Controllers.OData.OptionControllers using Core.DomainModel.ItSystemUsage; using Core.DomainServices; + [InternalApi] public class ArchiveLocationsController : BaseOptionController { public ArchiveLocationsController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/ArchiveTestLocationsController.cs b/Presentation.Web/Controllers/OData/OptionControllers/ArchiveTestLocationsController.cs index d47038f289..5827e807f9 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/ArchiveTestLocationsController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/ArchiveTestLocationsController.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { @@ -10,6 +7,7 @@ namespace Presentation.Web.Controllers.OData.OptionControllers using Core.DomainModel.ItSystemUsage; using Core.DomainServices; + [InternalApi] public class ArchiveTestLocationsController : BaseOptionController { public ArchiveTestLocationsController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/ArchiveTypesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/ArchiveTypesController.cs index 8b6bb28bbd..4a417484de 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/ArchiveTypesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/ArchiveTypesController.cs @@ -2,9 +2,11 @@ using Core.DomainModel.ItSystem; using Core.DomainServices; using Core.DomainModel.ItSystemUsage; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class ArchiveTypesController : BaseOptionController { public ArchiveTypesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/BusinessTypesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/BusinessTypesController.cs index d48a2cbbf3..2b06f470a5 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/BusinessTypesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/BusinessTypesController.cs @@ -1,9 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.ItSystem; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class BusinessTypesController : BaseOptionController { public BusinessTypesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/DataTypesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/DataTypesController.cs index 7959783e36..b99fe7a50e 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/DataTypesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/DataTypesController.cs @@ -1,9 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.ItSystem; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class DataTypesController : BaseOptionController { public DataTypesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/FrequencyTypesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/FrequencyTypesController.cs index 7b90fed7f1..9512412aed 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/FrequencyTypesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/FrequencyTypesController.cs @@ -2,9 +2,11 @@ using Core.DomainModel.ItSystem; using Core.DomainModel.ItSystemUsage; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class FrequencyTypesController : BaseOptionController { public FrequencyTypesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/GoalTypesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/GoalTypesController.cs index c6afea8a1f..afc35453ef 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/GoalTypesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/GoalTypesController.cs @@ -1,9 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.ItProject; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class GoalTypesController : BaseOptionController { public GoalTypesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/HandoverTrialTypesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/HandoverTrialTypesController.cs index 9454242e99..8231a7b153 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/HandoverTrialTypesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/HandoverTrialTypesController.cs @@ -1,9 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.ItContract; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class HandoverTrialTypesController : BaseOptionController { public HandoverTrialTypesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/InterfaceTypesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/InterfaceTypesController.cs index 2894e23bb7..d1a87ef7ff 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/InterfaceTypesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/InterfaceTypesController.cs @@ -1,9 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.ItSystem; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class InterfaceTypesController : BaseOptionController { public InterfaceTypesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/ItContractRolesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/ItContractRolesController.cs index ffbf33f58a..fb7df7192b 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/ItContractRolesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/ItContractRolesController.cs @@ -1,9 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.ItContract; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class ItContractRolesController : BaseOptionController { public ItContractRolesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/ItContractTemplateTypesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/ItContractTemplateTypesController.cs index da815f6fee..3cf3c937f1 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/ItContractTemplateTypesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/ItContractTemplateTypesController.cs @@ -1,9 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.ItContract; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class ItContractTemplateTypesController : BaseOptionController { public ItContractTemplateTypesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/ItContractTypesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/ItContractTypesController.cs index 18a2aaa4e8..b95340a63b 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/ItContractTypesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/ItContractTypesController.cs @@ -1,9 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.ItContract; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class ItContractTypesController : BaseOptionController { public ItContractTypesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/ItInterfaceTypesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/ItInterfaceTypesController.cs index fe65b60227..29edddf0d6 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/ItInterfaceTypesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/ItInterfaceTypesController.cs @@ -1,9 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.ItSystem; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class ItInterfaceTypesController : BaseOptionController { public ItInterfaceTypesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/ItProjectRolesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/ItProjectRolesController.cs index 19248f8fff..a0d8edea07 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/ItProjectRolesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/ItProjectRolesController.cs @@ -1,9 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.ItProject; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class ItProjectRolesController : BaseOptionController { public ItProjectRolesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/ItProjectTypesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/ItProjectTypesController.cs index b2d88c0ae1..b8b96acfe6 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/ItProjectTypesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/ItProjectTypesController.cs @@ -1,9 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.ItProject; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class ItProjectTypesController : BaseOptionController { public ItProjectTypesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/ItSystemCategoriesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/ItSystemCategoriesController.cs index 17e43e18e1..e124eb0013 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/ItSystemCategoriesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/ItSystemCategoriesController.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { @@ -10,6 +7,7 @@ namespace Presentation.Web.Controllers.OData.OptionControllers using Core.DomainModel.ItSystemUsage; using Core.DomainServices; + [InternalApi] public class ItSystemCategoriesController : BaseOptionController { public ItSystemCategoriesController( diff --git a/Presentation.Web/Controllers/OData/OptionControllers/ItSystemRolesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/ItSystemRolesController.cs index 2d8e4cd10b..2f3f60a1e1 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/ItSystemRolesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/ItSystemRolesController.cs @@ -1,9 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.ItSystem; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class ItSystemRolesController : BaseOptionController { public ItSystemRolesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/ItSystemTypesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/ItSystemTypesController.cs index 44d48d95ca..1923b4d106 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/ItSystemTypesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/ItSystemTypesController.cs @@ -1,9 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.ItSystem; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class ItSystemTypesController : BaseOptionController { public ItSystemTypesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/MethodTypesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/MethodTypesController.cs index efa823f939..ce93846a8f 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/MethodTypesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/MethodTypesController.cs @@ -1,9 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.ItSystem; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class MethodTypesController : BaseOptionController { public MethodTypesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/OptionExtendTypesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/OptionExtendTypesController.cs index 5e332bd86e..ad2c6cc337 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/OptionExtendTypesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/OptionExtendTypesController.cs @@ -1,9 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.ItContract; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class OptionExtendTypesController : BaseOptionController { public OptionExtendTypesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/OrganizationUnitRolesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/OrganizationUnitRolesController.cs index 7228a0431e..0cee454350 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/OrganizationUnitRolesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/OrganizationUnitRolesController.cs @@ -1,9 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.Organization; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class OrganizationUnitRolesController : BaseOptionController { public OrganizationUnitRolesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/PaymentFrequencyTypesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/PaymentFrequencyTypesController.cs index f9beb25cf0..a703e83c5f 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/PaymentFrequencyTypesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/PaymentFrequencyTypesController.cs @@ -1,9 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.ItContract; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class PaymentFrequencyTypesController : BaseOptionController { public PaymentFrequencyTypesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/PaymentModelTypesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/PaymentModelTypesController.cs index 3498bf27ce..e0d0bad7af 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/PaymentModelTypesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/PaymentModelTypesController.cs @@ -1,9 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.ItContract; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class PaymentModelTypesController : BaseOptionController { public PaymentModelTypesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/PriceRegulationTypesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/PriceRegulationTypesController.cs index 6556e5d1e5..7e1757c78d 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/PriceRegulationTypesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/PriceRegulationTypesController.cs @@ -1,9 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.ItContract; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class PriceRegulationTypesController : BaseOptionController { public PriceRegulationTypesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/ProcurementStrategyTypesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/ProcurementStrategyTypesController.cs index 9d66c2977c..45d5ab6939 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/ProcurementStrategyTypesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/ProcurementStrategyTypesController.cs @@ -1,9 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.ItContract; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class ProcurementStrategyTypesController : BaseOptionController { public ProcurementStrategyTypesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/PurchaseFormTypesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/PurchaseFormTypesController.cs index cd88f517ec..7b2864caa1 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/PurchaseFormTypesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/PurchaseFormTypesController.cs @@ -1,9 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.ItContract; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class PurchaseFormTypesController : BaseOptionController { public PurchaseFormTypesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/RegisterTypesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/RegisterTypesController.cs index f19782632c..3c8a17fb87 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/RegisterTypesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/RegisterTypesController.cs @@ -2,9 +2,11 @@ using Core.DomainModel.ItSystem; using Core.DomainModel.ItSystemUsage; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class RegisterTypesController : BaseOptionController { public RegisterTypesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/RegularPersonalDataTypesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/RegularPersonalDataTypesController.cs index 7ca1383042..16d23393d7 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/RegularPersonalDataTypesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/RegularPersonalDataTypesController.cs @@ -1,10 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.ItSystem; -using Core.DomainModel.ItSystemUsage; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class RegularPersonalDataTypesController : BaseOptionController { public RegularPersonalDataTypesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/ReportCategoriesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/ReportCategoriesController.cs index 09df90eb16..30a4d3d1ea 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/ReportCategoriesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/ReportCategoriesController.cs @@ -1,9 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.Reports; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class ReportCategoriesController : BaseEntityController { public ReportCategoriesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/ReportCategoryTypesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/ReportCategoryTypesController.cs index c2a593fac0..fad424a6e3 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/ReportCategoryTypesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/ReportCategoryTypesController.cs @@ -1,9 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.Reports; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData { + [InternalApi] public class ReportCategoryTypesController : BaseEntityController { public ReportCategoryTypesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/SensistivePersonalDataTypesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/SensistivePersonalDataTypesController.cs index d17ee66ed6..ea1e795b08 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/SensistivePersonalDataTypesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/SensistivePersonalDataTypesController.cs @@ -1,10 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.ItSystem; -using Core.DomainModel.ItSystemUsage; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class SensistivePersonalDataTypesController : BaseOptionController { public SensistivePersonalDataTypesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/SensitiveDataTypesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/SensitiveDataTypesController.cs index 41bef23182..ea284b073b 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/SensitiveDataTypesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/SensitiveDataTypesController.cs @@ -2,9 +2,11 @@ using Core.DomainModel.ItSystem; using Core.DomainModel.ItSystemUsage; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class SensitiveDataTypesController : BaseOptionController { public SensitiveDataTypesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/TerminationDeadlineTypesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/TerminationDeadlineTypesController.cs index 3ca42e1b03..01eddd9812 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/TerminationDeadlineTypesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/TerminationDeadlineTypesController.cs @@ -1,9 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.ItContract; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class TerminationDeadlineTypesController : BaseOptionController { public TerminationDeadlineTypesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OptionControllers/TsaTypesController.cs b/Presentation.Web/Controllers/OData/OptionControllers/TsaTypesController.cs index 7be6e8ba09..04bd65946c 100644 --- a/Presentation.Web/Controllers/OData/OptionControllers/TsaTypesController.cs +++ b/Presentation.Web/Controllers/OData/OptionControllers/TsaTypesController.cs @@ -1,9 +1,11 @@ using Core.ApplicationServices; using Core.DomainModel.ItSystem; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData.OptionControllers { + [InternalApi] public class TsaTypesController : BaseOptionController { public TsaTypesController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/OrganizationRightsController.cs b/Presentation.Web/Controllers/OData/OrganizationRightsController.cs index 49955c3b88..b1d9871f48 100644 --- a/Presentation.Web/Controllers/OData/OrganizationRightsController.cs +++ b/Presentation.Web/Controllers/OData/OrganizationRightsController.cs @@ -7,9 +7,11 @@ using Core.ApplicationServices; using Core.DomainServices; using Core.DomainModel.Organization; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData { + [InternalApi] public class OrganizationRightsController : BaseEntityController { private readonly IUserService _userService; @@ -36,15 +38,38 @@ public IHttpActionResult GetRights(int orgKey) public IHttpActionResult PostRights(int orgKey, OrganizationRight entity) { if (!ModelState.IsValid) + { return BadRequest(ModelState); + } + + var user = _userService.GetUserById(UserId); + + if(entity.Role == OrganizationRole.GlobalAdmin) + { + if(!user.IsGlobalAdmin) + { + return Forbidden(); + } + } + + if(entity.Role == OrganizationRole.LocalAdmin) + { + if(!user.IsGlobalAdmin && !user.IsLocalAdmin) + { + return Forbidden(); + } + } entity.OrganizationId = orgKey; entity.ObjectOwnerId = UserId; - entity.LastChangedByUserId = UserId; if (!_authService.HasWriteAccess(UserId, entity) && !_authService.IsLocalAdmin(this.UserId)) - return StatusCode(HttpStatusCode.Forbidden); + { + return Forbidden(); + } + entity.LastChangedByUserId = UserId; + try { entity = Repository.Insert(entity); @@ -58,16 +83,48 @@ public IHttpActionResult PostRights(int orgKey, OrganizationRight entity) return Created(entity); } + /// + /// Always Use 403 - POST /Organizations(orgKey)/Rights instead + /// + /// + /// + public override IHttpActionResult Post(OrganizationRight entity) + { + return StatusCode(HttpStatusCode.Forbidden); + } + // DELETE /Organizations(1)/Rights(1) [ODataRoute("Organizations({orgKey})/Rights({key})")] public IHttpActionResult DeleteRights(int orgKey, int key) { var entity = Repository.AsQueryable().SingleOrDefault(m => m.OrganizationId == orgKey && m.Id == key); if (entity == null) + { return NotFound(); + } + + var user = _userService.GetUserById(UserId); + + if (entity.Role == OrganizationRole.GlobalAdmin) + { + if (!user.IsGlobalAdmin) + { + return Forbidden(); + } + } + + if (entity.Role == OrganizationRole.LocalAdmin) + { + if (!user.IsGlobalAdmin && !user.IsLocalAdmin) + { + return Forbidden(); + } + } if (!_authService.HasWriteAccess(UserId, entity) && !_authService.IsLocalAdmin(this.UserId)) - return StatusCode(HttpStatusCode.Forbidden); + { + return Forbidden(); + } try { @@ -89,7 +146,27 @@ public override IHttpActionResult Delete(int key) return NotFound(); if (!_authService.HasWriteAccess(UserId, entity) && !_authService.IsLocalAdmin(this.UserId)) - return Unauthorized(); + { + return Forbidden(); + } + + var user = _userService.GetUserById(UserId); + + if (entity.Role == OrganizationRole.GlobalAdmin) + { + if (!user.IsGlobalAdmin) + { + return Forbidden(); + } + } + + if (entity.Role == OrganizationRole.LocalAdmin) + { + if (!user.IsGlobalAdmin && !user.IsLocalAdmin) + { + return Forbidden(); + } + } try { @@ -110,22 +187,21 @@ public override IHttpActionResult Patch(int key, Delta delta) // does the entity exist? if (entity == null) + { return NotFound(); + } // check if user is allowed to write to the entity if (!_authService.HasWriteAccess(UserId, entity) && !_authService.IsLocalAdmin(this.UserId)) - return StatusCode(HttpStatusCode.Forbidden); - - //Check if user is allowed to set accessmodifier to public - //var accessModifier = (entity as IHasAccessModifier)?.AccessModifier; - //if (accessModifier == AccessModifier.Public && !_authService.CanExecute(UserId, Feature.CanSetAccessModifierToPublic)) - //{ - // return Unauthorized(); - //} + { + return Forbidden(); + } // check model state if (!ModelState.IsValid) + { return BadRequest(ModelState); + } try { diff --git a/Presentation.Web/Controllers/OData/OrganizationUnitRightsController.cs b/Presentation.Web/Controllers/OData/OrganizationUnitRightsController.cs index 964e4ba3cc..9c5a082a64 100644 --- a/Presentation.Web/Controllers/OData/OrganizationUnitRightsController.cs +++ b/Presentation.Web/Controllers/OData/OrganizationUnitRightsController.cs @@ -4,14 +4,14 @@ using System.Web.OData.Routing; using System.Web.Http; using System.Linq; +using System; +using System.Net; +using Core.ApplicationServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData { - using System; - using System.Net; - - using Core.ApplicationServices; - + [InternalApi] public class OrganizationUnitRightsController : BaseEntityController { private readonly IAuthenticationService _authService; @@ -46,10 +46,14 @@ public override IHttpActionResult Delete(int key) var entity = Repository.GetByKey(key); if (entity == null) + { return NotFound(); + } if (!_authService.HasWriteAccess(UserId, entity) && !_authService.IsLocalAdmin(this.UserId)) + { return Unauthorized(); + } try { @@ -70,22 +74,21 @@ public override IHttpActionResult Patch(int key, Delta de // does the entity exist? if (entity == null) + { return NotFound(); + } // check if user is allowed to write to the entity if (!_authService.HasWriteAccess(UserId, entity) && !_authService.IsLocalAdmin(this.UserId)) - return StatusCode(HttpStatusCode.Forbidden); - - //Check if user is allowed to set accessmodifier to public - //var accessModifier = (entity as IHasAccessModifier)?.AccessModifier; - //if (accessModifier == AccessModifier.Public && !_authService.CanExecute(UserId, Feature.CanSetAccessModifierToPublic)) - //{ - // return Unauthorized(); - //} + { + return Forbidden(); + } // check model state if (!ModelState.IsValid) + { return BadRequest(ModelState); + } try { diff --git a/Presentation.Web/Controllers/OData/OrganizationUnitsController.cs b/Presentation.Web/Controllers/OData/OrganizationUnitsController.cs index 5577bba0b7..6b7b559157 100644 --- a/Presentation.Web/Controllers/OData/OrganizationUnitsController.cs +++ b/Presentation.Web/Controllers/OData/OrganizationUnitsController.cs @@ -3,12 +3,13 @@ using System.Web.OData; using System.Web.OData.Routing; using Core.DomainServices; -using System.Net; using Core.DomainModel.Organization; using Core.ApplicationServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData { + [InternalApi] public class OrganizationUnitsController : BaseEntityController { private readonly IAuthenticationService _authService; @@ -42,7 +43,7 @@ public IHttpActionResult GetOrganizationUnits(int orgKey) var loggedIntoOrgId = _authService.GetCurrentOrganizationId(UserId); if (loggedIntoOrgId != orgKey && !_authService.HasReadAccessOutsideContext(UserId)) { - return StatusCode(HttpStatusCode.Forbidden); + return Forbidden(); } var result = Repository.AsQueryable().Where(m => m.OrganizationId == orgKey); @@ -61,7 +62,7 @@ public IHttpActionResult GetOrganizationUnit(int orgKey, int unitKey) if (_authService.HasReadAccess(UserId, entity)) return Ok(entity); - return StatusCode(HttpStatusCode.Forbidden); + return Forbidden(); } } } diff --git a/Presentation.Web/Controllers/OData/OrganizationsController.cs b/Presentation.Web/Controllers/OData/OrganizationsController.cs index 3654d31df8..4879896ec6 100644 --- a/Presentation.Web/Controllers/OData/OrganizationsController.cs +++ b/Presentation.Web/Controllers/OData/OrganizationsController.cs @@ -9,9 +9,12 @@ using System.Web.OData; using System.Web.OData.Routing; using Core.DomainModel; +using System.Linq; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData { + [InternalApi] public class OrganizationsController : BaseEntityController { private readonly IOrganizationService _organizationService; @@ -28,18 +31,24 @@ public OrganizationsController(IGenericRepository repository, IOrg _userRepository = userRepository; } - [ODataRoute("Organizations({orgKey})/RemoveUser")] - public IHttpActionResult DeleteRemoveUserFromOrganization(int orgKey, ODataActionParameters parameters) + [HttpPost] + public IHttpActionResult RemoveUser([FromODataUri]int orgKey, ODataActionParameters parameters) { if (!ModelState.IsValid) + { return BadRequest(ModelState); + } var entity = Repository.GetByKey(orgKey); if (entity == null) + { return NotFound(); + } if (!_authService.HasWriteAccess(UserId, entity)) - return Unauthorized(); + { + return Forbidden(); + } var userId = 0; if (parameters.ContainsKey("userId")) @@ -59,7 +68,9 @@ public virtual IHttpActionResult GetLastChangedByUser(int orgKey) { var loggedIntoOrgId = _authService.GetCurrentOrganizationId(UserId); if (loggedIntoOrgId != orgKey && !_authService.HasReadAccessOutsideContext(UserId)) - return StatusCode(HttpStatusCode.Forbidden); + { + return Forbidden(); + } var result = Repository.GetByKey(orgKey).LastChangedByUser; return Ok(result); @@ -72,7 +83,7 @@ public virtual IHttpActionResult GetObjectOwner(int orgKey) var loggedIntoOrgId = _authService.GetCurrentOrganizationId(UserId); if (loggedIntoOrgId != orgKey && !_authService.HasReadAccessOutsideContext(UserId)) { - return StatusCode(HttpStatusCode.Forbidden); + return Forbidden(); } var result = Repository.GetByKey(orgKey).ObjectOwner; @@ -86,7 +97,7 @@ public virtual IHttpActionResult GetType(int orgKey) var loggedIntoOrgId = _authService.GetCurrentOrganizationId(UserId); if (loggedIntoOrgId != orgKey && !_authService.HasReadAccessOutsideContext(UserId)) { - return StatusCode(HttpStatusCode.Forbidden); + return Forbidden(); } var result = Repository.GetByKey(orgKey).Type; @@ -99,7 +110,7 @@ public override IHttpActionResult Post(Organization organization) var loggedIntoOrgId = _authService.GetCurrentOrganizationId(UserId); if (loggedIntoOrgId != organization.Id && !_authService.HasReadAccessOutsideContext(UserId)) { - return StatusCode(HttpStatusCode.Forbidden); + return Forbidden(); } var user = _userRepository.GetByKey(UserId); @@ -126,9 +137,22 @@ public override IHttpActionResult Post(Organization organization) return Created(organization); } + [EnableQuery] + public IHttpActionResult GetUsers([FromODataUri] int key) + { + var loggedIntoOrgId = _authService.GetCurrentOrganizationId(UserId); + if (loggedIntoOrgId != key && !_authService.HasReadAccessOutsideContext(UserId)) + { + return Forbidden(); + } + + var result = _userRepository.AsQueryable().Where(m => m.OrganizationRights.Any(r => r.OrganizationId == key)); + return Ok(result); + } + public override IHttpActionResult Patch(int key, Delta delta) { - var organization = delta.GetEntity(); + var organization = delta.GetInstance(); CheckOrgTypeRights(organization); return base.Patch(key, delta); diff --git a/Presentation.Web/Controllers/OData/ReportsController.cs b/Presentation.Web/Controllers/OData/ReportsController.cs index 2ce5aa6855..d94e19ab47 100644 --- a/Presentation.Web/Controllers/OData/ReportsController.cs +++ b/Presentation.Web/Controllers/OData/ReportsController.cs @@ -3,9 +3,11 @@ using Core.DomainModel.Reports; using Core.DomainServices; using Core.ApplicationServices; +using Presentation.Web.Infrastructure.Attributes; namespace Presentation.Web.Controllers.OData { + [InternalApi] public class ReportsController : BaseEntityController { public ReportsController(IGenericRepository repository, IAuthenticationService authService) diff --git a/Presentation.Web/Controllers/OData/ReportsControllers/BaseOdataAuthorizationController.cs b/Presentation.Web/Controllers/OData/ReportsControllers/BaseOdataAuthorizationController.cs index 5c13eed0d4..9615926506 100644 --- a/Presentation.Web/Controllers/OData/ReportsControllers/BaseOdataAuthorizationController.cs +++ b/Presentation.Web/Controllers/OData/ReportsControllers/BaseOdataAuthorizationController.cs @@ -1,17 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Web.Http; +using System.Web.Http; using System.Web.OData; -using System.Web.OData.Extensions; using Core.DomainServices; -using System.Web.OData.Routing; -using Microsoft.OData.Core; -using Microsoft.OData.Core.UriParser; using Ninject; using Ninject.Extensions.Logging; -using System.Web.Http.Routing; namespace Presentation.Web.Controllers.OData.ReportsControllers { diff --git a/Presentation.Web/Controllers/OData/ReportsControllers/ReportsITSystemContactsController.cs b/Presentation.Web/Controllers/OData/ReportsControllers/ReportsITSystemContactsController.cs index 84cbf7efd5..300e16b93d 100644 --- a/Presentation.Web/Controllers/OData/ReportsControllers/ReportsITSystemContactsController.cs +++ b/Presentation.Web/Controllers/OData/ReportsControllers/ReportsITSystemContactsController.cs @@ -1,22 +1,21 @@ using System; using Core.ApplicationServices; -using Core.DomainModel.Organization; using Core.DomainServices; using System.Net; -using System.Security; -using System.Threading; using System.Web.Http; using System.Web.OData; using System.Web.OData.Routing; -using Core.DomainModel; -using System.Linq; using Presentation.Web.Controllers.OData.ReportsControllers; using Core.DomainModel.ItSystem; using System.Collections.Generic; +using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models; +using Swashbuckle.OData; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.OData { + [InternalApi] public class ReportsITSystemContactsController : BaseOdataAuthorizationController { private readonly IAuthenticationService _authService; @@ -28,6 +27,8 @@ public ReportsITSystemContactsController(IGenericRepository repos [HttpGet] [EnableQuery] [ODataRoute("ReportsITSystemContacts")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse>))] + [SwaggerResponse(HttpStatusCode.Forbidden)] public IHttpActionResult Get() { if (!_authService.HasReadAccessOutsideContext(UserId)) diff --git a/Presentation.Web/Controllers/OData/ReportsControllers/ReportsItSystemRolesController.cs b/Presentation.Web/Controllers/OData/ReportsControllers/ReportsItSystemRolesController.cs index 63341dbba9..9d5b3ac740 100644 --- a/Presentation.Web/Controllers/OData/ReportsControllers/ReportsItSystemRolesController.cs +++ b/Presentation.Web/Controllers/OData/ReportsControllers/ReportsItSystemRolesController.cs @@ -1,20 +1,19 @@ -using System; -using Core.ApplicationServices; -using Core.DomainModel.Organization; +using Core.ApplicationServices; using Core.DomainServices; using System.Net; -using System.Security; -using System.Threading; using System.Web.Http; using System.Web.OData; using System.Web.OData.Routing; -using Core.DomainModel; using System.Linq; using Presentation.Web.Controllers.OData.ReportsControllers; using Core.DomainModel.ItSystem; +using Presentation.Web.Infrastructure.Attributes; +using Swashbuckle.OData; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.OData { + [InternalApi] public class ReportsItSystemRolesController : BaseOdataAuthorizationController { private readonly IAuthenticationService _authService; @@ -26,6 +25,8 @@ public ReportsItSystemRolesController(IGenericRepository repositor [HttpGet] [EnableQuery] [ODataRoute("ReportsItSystemRoles")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse>))] + [SwaggerResponse(HttpStatusCode.Forbidden)] public IHttpActionResult Get() { if (!_authService.HasReadAccessOutsideContext(UserId)) diff --git a/Presentation.Web/Controllers/OData/ReportsControllers/ReportsItSystemsController.cs b/Presentation.Web/Controllers/OData/ReportsControllers/ReportsItSystemsController.cs index 495ebc3db7..0567510404 100644 --- a/Presentation.Web/Controllers/OData/ReportsControllers/ReportsItSystemsController.cs +++ b/Presentation.Web/Controllers/OData/ReportsControllers/ReportsItSystemsController.cs @@ -1,20 +1,19 @@ -using System; -using Core.ApplicationServices; -using Core.DomainModel.Organization; +using Core.ApplicationServices; using Core.DomainServices; using System.Net; -using System.Security; -using System.Threading; using System.Web.Http; using System.Web.OData; using System.Web.OData.Routing; -using Core.DomainModel; using System.Linq; using Presentation.Web.Controllers.OData.ReportsControllers; using Core.DomainModel.ItSystem; +using Presentation.Web.Infrastructure.Attributes; +using Swashbuckle.OData; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.OData { + [InternalApi] public class ReportsItSystemsController : BaseOdataAuthorizationController { private readonly IAuthenticationService _authService; @@ -26,6 +25,8 @@ public ReportsItSystemsController(IGenericRepository repository, IAuth [HttpGet] [EnableQuery] [ODataRoute("ReportsItSystems")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse>))] + [SwaggerResponse(HttpStatusCode.Forbidden)] public IHttpActionResult Get() { if (!_authService.HasReadAccessOutsideContext(UserId)) diff --git a/Presentation.Web/Controllers/OData/ReportsControllers/ReportsMunicipalitiesController.cs b/Presentation.Web/Controllers/OData/ReportsControllers/ReportsMunicipalitiesController.cs index 69f301be88..fa8c4cbc53 100644 --- a/Presentation.Web/Controllers/OData/ReportsControllers/ReportsMunicipalitiesController.cs +++ b/Presentation.Web/Controllers/OData/ReportsControllers/ReportsMunicipalitiesController.cs @@ -1,19 +1,19 @@ -using System; -using Core.ApplicationServices; +using Core.ApplicationServices; using Core.DomainModel.Organization; using Core.DomainServices; using System.Net; -using System.Security; -using System.Threading; using System.Web.Http; using System.Web.OData; using System.Web.OData.Routing; -using Core.DomainModel; using System.Linq; using Presentation.Web.Controllers.OData.ReportsControllers; +using Presentation.Web.Infrastructure.Attributes; +using Swashbuckle.OData; +using Swashbuckle.Swagger.Annotations; namespace Presentation.Web.Controllers.OData { + [InternalApi] public class ReportsMunicipalitiesController : BaseOdataAuthorizationController { private readonly IAuthenticationService _authService; @@ -25,6 +25,8 @@ public ReportsMunicipalitiesController(IGenericRepository reposito [HttpGet] [EnableQuery] [ODataRoute("ReportsMunicipalities")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse>))] + [SwaggerResponse(HttpStatusCode.Forbidden)] public IHttpActionResult Get() { if (!_authService.HasReadAccessOutsideContext(UserId)) diff --git a/Presentation.Web/Controllers/OData/UsersController.cs b/Presentation.Web/Controllers/OData/UsersController.cs index bcaa95d2db..82e3f0e02d 100644 --- a/Presentation.Web/Controllers/OData/UsersController.cs +++ b/Presentation.Web/Controllers/OData/UsersController.cs @@ -1,6 +1,7 @@ using Core.ApplicationServices; using Core.DomainModel; using Core.DomainServices; +using Presentation.Web.Infrastructure.Attributes; using System.Linq; using System.Net; using System.Web.Http; @@ -9,6 +10,7 @@ namespace Presentation.Web.Controllers.OData { + [InternalApi] public class UsersController : BaseEntityController { private readonly IAuthenticationService _authService; @@ -28,8 +30,8 @@ public override IHttpActionResult Post(User entity) return StatusCode(HttpStatusCode.MethodNotAllowed); } - [ODataRoute("Users/Create")] - public IHttpActionResult PostCreate(ODataActionParameters parameters) + [HttpPost] + public IHttpActionResult Create(ODataActionParameters parameters) { if (!ModelState.IsValid) { @@ -81,16 +83,10 @@ public IHttpActionResult PostCreate(ODataActionParameters parameters) return Created(createdUser); } - - [ODataRoute("Users/IsEmailAvailable(email={email})")] - public IHttpActionResult GetIsEmailAvailable(string email) + [HttpGet] + public IHttpActionResult IsEmailAvailable(string email) { - // strip strange single quotes from parameter - // http://stackoverflow.com/questions/39510551/string-parameter-to-bound-function-contains-single-quotes - var strippedEmail = email.Remove(0, 1); - strippedEmail = strippedEmail.Remove(strippedEmail.Length-1); - - if (EmailExists(strippedEmail)) + if (EmailExists(email)) return Ok(false); else return Ok(true); @@ -99,12 +95,7 @@ public IHttpActionResult GetIsEmailAvailable(string email) [ODataRoute("GetUserByEmail(email={email})")] public IHttpActionResult GetUserByEmail(string email) { - // strip strange single quotes from parameter - // http://stackoverflow.com/questions/39510551/string-parameter-to-bound-function-contains-single-quotes - var strippedEmail = email.Remove(0, 1); - strippedEmail = strippedEmail.Remove(strippedEmail.Length - 1); - - var userToReturn = this._repository.AsQueryable().FirstOrDefault(u => u.Email.ToLower() == strippedEmail.ToLower()); + var userToReturn = this._repository.AsQueryable().FirstOrDefault(u => u.Email.ToLower() == email.ToLower()); if(userToReturn != null) { return Ok(userToReturn); @@ -112,6 +103,16 @@ public IHttpActionResult GetUserByEmail(string email) return NotFound(); } + /// + /// Always returns 401 - Unauthorized. Please use /api/User/{id} from API - UserController instead. + /// + /// + /// + public override IHttpActionResult Delete(int key) + { + return Unauthorized(); + } + //GET /Organizations(1)/DefaultOrganizationForUsers [EnableQuery] [ODataRoute("Organizations({orgKey})/DefaultOrganizationForUsers")] @@ -119,25 +120,14 @@ public IHttpActionResult GetDefaultOrganizationForUsers(int orgKey) { var loggedIntoOrgId = _authService.GetCurrentOrganizationId(UserId); if (loggedIntoOrgId != orgKey && !_authService.HasReadAccessOutsideContext(UserId)) - return StatusCode(HttpStatusCode.Forbidden); + { + return Forbidden(); + } var result = Repository.AsQueryable().Where(m => m.DefaultOrganizationId == orgKey); return Ok(result); } - //GET /Organizations(1)/Users - [EnableQuery] - [ODataRoute("Organizations({orgKey})/Users")] - public IHttpActionResult GetByOrganization(int orgKey) - { - var loggedIntoOrgId = _authService.GetCurrentOrganizationId(UserId); - if (loggedIntoOrgId != orgKey && !_authService.HasReadAccessOutsideContext(UserId)) - return StatusCode(HttpStatusCode.Forbidden); - - var result = Repository.AsQueryable().Where(m => m.OrganizationRights.Any(r=> r.OrganizationId == orgKey)); - return Ok(result); - } - private bool EmailExists(string email) { var matchingEmails = Repository.Get(x => x.Email == email); diff --git a/Presentation.Web/Extensions/HttpMethodIntent.cs b/Presentation.Web/Extensions/HttpMethodIntent.cs new file mode 100644 index 0000000000..c5efbbb4a4 --- /dev/null +++ b/Presentation.Web/Extensions/HttpMethodIntent.cs @@ -0,0 +1,24 @@ +namespace Presentation.Web.Extensions +{ + public static class HttpMethodIntent + { + public static bool IsMutation(this string method) + { + if (method == null) + { + return false; + } + switch (method.ToLowerInvariant()) + { + case "post": + case "put": + case "patch": + case "delete": + return true; + default: + return false; + } + } + + } +} \ No newline at end of file diff --git a/Presentation.Web/Extensions/IdentityClaimExtension.cs b/Presentation.Web/Extensions/IdentityClaimExtension.cs new file mode 100644 index 0000000000..3aaed52d6a --- /dev/null +++ b/Presentation.Web/Extensions/IdentityClaimExtension.cs @@ -0,0 +1,13 @@ +using System.Linq; +using System.Security.Claims; + +namespace Presentation.Web.Extensions +{ + public static class IdentityClaimExtension + { + public static Claim GetClaimOrNull(this ClaimsIdentity claimHolder, string claimName) + { + return claimHolder.FindAll(x => x.Type == claimName).FirstOrDefault(); + } + } +} \ No newline at end of file diff --git a/Presentation.Web/Extensions/OwinContextExtensions.cs b/Presentation.Web/Extensions/OwinContextExtensions.cs new file mode 100644 index 0000000000..f6a4553f62 --- /dev/null +++ b/Presentation.Web/Extensions/OwinContextExtensions.cs @@ -0,0 +1,25 @@ +using Microsoft.Owin; + +namespace Presentation.Web.Extensions +{ + public static class OwinContextExtensions + { + private const string Prefix = nameof(OwinContextExtensions); + + public static IOwinContext WithEnvironmentProperty(this IOwinContext context, T value) + { + context.Set(GetPropertyName(), value); + return context; + } + + public static T GetEnvironmentProperty(this IOwinContext context) + { + return context.Get(GetPropertyName()); + } + + private static string GetPropertyName() + { + return $"{Prefix}_{typeof(T).Name}"; + } + } +} \ No newline at end of file diff --git a/Presentation.Web/Global.asax.cs b/Presentation.Web/Global.asax.cs index 573f359ca8..94c961a8ec 100644 --- a/Presentation.Web/Global.asax.cs +++ b/Presentation.Web/Global.asax.cs @@ -4,7 +4,6 @@ using System.Web.Routing; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; -using Presentation.Web.App_Start; namespace Presentation.Web { @@ -16,7 +15,6 @@ public class MvcApplication : System.Web.HttpApplication protected void Application_Start() { LogConfig.RegisterLog(); - //GlobalConfiguration.Configuration.MessageHandlers.Add(new MessageLoggingHandler()); AreaRegistration.RegisterAllAreas(); GlobalConfiguration.Configure(WebApiConfig.Register); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); diff --git a/Presentation.Web/Helpers/Constants.cs b/Presentation.Web/Helpers/Constants.cs new file mode 100644 index 0000000000..63c67f05c7 --- /dev/null +++ b/Presentation.Web/Helpers/Constants.cs @@ -0,0 +1,12 @@ +namespace Presentation.Web.Helpers +{ + public static class Constants + { + public static class StatusCodeMessages + { + public const string ForbiddenErrorMessage = "Du har ikke rettigheder til at bruge denne funktion"; + public const string UnauthorizedErrorMessage = "Du har ikke adgang til denne funktion log ind med en bruger og prøv igen"; + + } + } +} \ No newline at end of file diff --git a/Presentation.Web/Infrastructure/Attributes/InternalApiAttribute.cs b/Presentation.Web/Infrastructure/Attributes/InternalApiAttribute.cs new file mode 100644 index 0000000000..4233627668 --- /dev/null +++ b/Presentation.Web/Infrastructure/Attributes/InternalApiAttribute.cs @@ -0,0 +1,31 @@ +using Presentation.Web.Infrastructure.Model.Authentication; +using System.Net; +using System.Net.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Filters; +using Core.ApplicationServices.Authentication; + +namespace Presentation.Web.Infrastructure.Attributes +{ + public class InternalApiAttribute : ActionFilterAttribute + { + public override void OnActionExecuting(HttpActionContext actionContext) + { + var authContext = (IAuthenticationContext)actionContext.ControllerContext.Configuration.DependencyResolver.GetService(typeof(IAuthenticationContext)); + + if (authContext.Method == AuthenticationMethod.KitosToken) + { + actionContext.Response = new HttpResponseMessage() + { + StatusCode = HttpStatusCode.Forbidden, + Content = new StringContent("Det er ikke tilladt at benytte dette endpoint") + }; + + } + base.OnActionExecuting(actionContext); + + + } + + } +} diff --git a/Presentation.Web/Infrastructure/Attributes/PublicApiAttribute.cs b/Presentation.Web/Infrastructure/Attributes/PublicApiAttribute.cs new file mode 100644 index 0000000000..c16679d3ee --- /dev/null +++ b/Presentation.Web/Infrastructure/Attributes/PublicApiAttribute.cs @@ -0,0 +1,8 @@ +using System; + +namespace Presentation.Web.Infrastructure.Attributes +{ + public class PublicApiAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/Presentation.Web/Infrastructure/Authorization/Controller/ContextBasedAuthorizationStrategy.cs b/Presentation.Web/Infrastructure/Authorization/Controller/ContextBasedAuthorizationStrategy.cs new file mode 100644 index 0000000000..206189b42a --- /dev/null +++ b/Presentation.Web/Infrastructure/Authorization/Controller/ContextBasedAuthorizationStrategy.cs @@ -0,0 +1,57 @@ +using Core.ApplicationServices.Authorization; +using Core.DomainModel; +using Core.DomainServices.Authorization; + +namespace Presentation.Web.Infrastructure.Authorization.Controller +{ + public class ContextBasedAuthorizationStrategy : IControllerAuthorizationStrategy + { + private readonly IAuthorizationContext _authorizationContext; + + public ContextBasedAuthorizationStrategy(IAuthorizationContext authorizationContext) + { + _authorizationContext = authorizationContext; + } + + public CrossOrganizationDataReadAccessLevel GetCrossOrganizationReadAccess() + { + return _authorizationContext.GetCrossOrganizationReadAccess(); + } + + public OrganizationDataReadAccessLevel GetOrganizationReadAccessLevel(int organizationId) + { + return _authorizationContext.GetOrganizationReadAccessLevel(organizationId); + } + + public bool AllowRead(IEntity entity) + { + return _authorizationContext.AllowReads(entity); + } + + public bool AllowCreate(IEntity entity) + { + //Entity instance is not used going forward + return _authorizationContext.AllowCreate(entity); + } + + public bool AllowCreate() + { + return _authorizationContext.AllowCreate(); + } + + public bool AllowModify(IEntity entity) + { + return _authorizationContext.AllowModify(entity); + } + + public bool AllowDelete(IEntity entity) + { + return _authorizationContext.AllowDelete(entity); + } + + public bool AllowEntityVisibilityControl(IEntity entity) + { + return _authorizationContext.AllowEntityVisibilityControl(entity); + } + } +} \ No newline at end of file diff --git a/Presentation.Web/Infrastructure/Authorization/Controller/IControllerAuthorizationStrategy.cs b/Presentation.Web/Infrastructure/Authorization/Controller/IControllerAuthorizationStrategy.cs new file mode 100644 index 0000000000..2cea249895 --- /dev/null +++ b/Presentation.Web/Infrastructure/Authorization/Controller/IControllerAuthorizationStrategy.cs @@ -0,0 +1,17 @@ +using Core.DomainModel; +using Core.DomainServices.Authorization; + +namespace Presentation.Web.Infrastructure.Authorization.Controller +{ + public interface IControllerAuthorizationStrategy + { + CrossOrganizationDataReadAccessLevel GetCrossOrganizationReadAccess(); + OrganizationDataReadAccessLevel GetOrganizationReadAccessLevel(int organizationId); + bool AllowRead(IEntity entity); + bool AllowCreate(IEntity entity); + bool AllowCreate(); + bool AllowModify(IEntity entity); + bool AllowDelete(IEntity entity); + bool AllowEntityVisibilityControl(IEntity entity); + } +} diff --git a/Presentation.Web/Infrastructure/Authorization/Controller/LegacyAuthorizationStrategy.cs b/Presentation.Web/Infrastructure/Authorization/Controller/LegacyAuthorizationStrategy.cs new file mode 100644 index 0000000000..05b31994a0 --- /dev/null +++ b/Presentation.Web/Infrastructure/Authorization/Controller/LegacyAuthorizationStrategy.cs @@ -0,0 +1,75 @@ +using System; +using Core.ApplicationServices; +using Core.DomainModel; +using Core.DomainServices.Authorization; + +namespace Presentation.Web.Infrastructure.Authorization.Controller +{ + public class LegacyAuthorizationStrategy : IControllerAuthorizationStrategy + { + private readonly IAuthenticationService _authenticationService; + private readonly Func _userId; + + public LegacyAuthorizationStrategy(IAuthenticationService authenticationService, Func userId) + { + _authenticationService = authenticationService; + _userId = userId; + } + + public CrossOrganizationDataReadAccessLevel GetCrossOrganizationReadAccess() + { + var userId = _userId(); + + if (_authenticationService.IsGlobalAdmin(userId)) + { + return CrossOrganizationDataReadAccessLevel.All; + } + + return _authenticationService.HasReadAccessOutsideContext(userId) + ? CrossOrganizationDataReadAccessLevel.Public + : CrossOrganizationDataReadAccessLevel.None; + } + + public OrganizationDataReadAccessLevel GetOrganizationReadAccessLevel(int organizationId) + { + if (_authenticationService.HasReadAccessOutsideContext(_userId()) || + _authenticationService.GetCurrentOrganizationId(_userId()) == organizationId) + { + //The legacy authorization was a binary decision. Even if municipality users should not see local data from other orgs the check allowed id and was rescued of the way KITOS UI asked for data. + return OrganizationDataReadAccessLevel.All; + } + return OrganizationDataReadAccessLevel.None; + } + + public bool AllowRead(IEntity entity) + { + return _authenticationService.HasReadAccess(_userId(), entity); + } + + public bool AllowCreate(IEntity entity) + { + //Old strategy was hard coded in a lot of controllers and otherwise they created an instance and asked for modificationaccess + return AllowModify(entity); + } + + public bool AllowCreate() + { + return false; + } + + public bool AllowModify(IEntity entity) + { + return _authenticationService.HasWriteAccess(_userId(), entity); + } + + public bool AllowDelete(IEntity entity) + { + return AllowModify(entity); + } + + public bool AllowEntityVisibilityControl(IEntity entity) + { + return _authenticationService.CanExecute(_userId(), Feature.CanSetAccessModifierToPublic); + } + } +} \ No newline at end of file diff --git a/Presentation.Web/Infrastructure/CustomMembershipProvider.cs b/Presentation.Web/Infrastructure/CustomMembershipProvider.cs index f73841fb85..17fe3761b5 100644 --- a/Presentation.Web/Infrastructure/CustomMembershipProvider.cs +++ b/Presentation.Web/Infrastructure/CustomMembershipProvider.cs @@ -2,7 +2,7 @@ using System.Collections.Specialized; using System.Web.Security; using Core.DomainModel; -using Core.DomainServices; +using Infrastructure.Services.Cryptography; using Ninject; using Ninject.Extensions.Logging; diff --git a/Presentation.Web/Infrastructure/ExceptionLogFilterAttribute.cs b/Presentation.Web/Infrastructure/ExceptionLogFilterAttribute.cs index 9560f2f7ae..966f91cd6c 100644 --- a/Presentation.Web/Infrastructure/ExceptionLogFilterAttribute.cs +++ b/Presentation.Web/Infrastructure/ExceptionLogFilterAttribute.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.Http.Filters; +using System.Web.Http.Filters; using Serilog; namespace Presentation.Web.Infrastructure diff --git a/Presentation.Web/Infrastructure/Factories/Authentication/OwinAuthenticationContextFactory.cs b/Presentation.Web/Infrastructure/Factories/Authentication/OwinAuthenticationContextFactory.cs new file mode 100644 index 0000000000..68103eabe5 --- /dev/null +++ b/Presentation.Web/Infrastructure/Factories/Authentication/OwinAuthenticationContextFactory.cs @@ -0,0 +1,109 @@ +using System.Security.Claims; +using System.Security.Principal; +using Core.ApplicationServices.Authentication; +using Core.DomainModel; +using Core.DomainServices; +using Microsoft.Owin; +using Presentation.Web.Extensions; +using Presentation.Web.Infrastructure.Model.Authentication; +using Serilog; + +namespace Presentation.Web.Infrastructure.Factories.Authentication +{ + public class OwinAuthenticationContextFactory : IAuthenticationContextFactory + { + private readonly ILogger _logger; + private readonly IOwinContext _owinContext; + private readonly IUserRepository _userRepository; + + public OwinAuthenticationContextFactory(ILogger logger, IOwinContext owinContext, IUserRepository userRepository) + { + _logger = logger; + _owinContext = owinContext; + _userRepository = userRepository; + } + + public IAuthenticationContext Create() + { + var principal = _owinContext.Authentication.User; + var user = GetAuthenticatedUser(principal); + return user != null + ? new AuthenticationContext(MapAuthenticationMethod(principal), MapApiAccess(user), user.Id, MapOrganizationId(user, principal)) + : new AuthenticationContext(AuthenticationMethod.Anonymous, false); + } + + private bool MapApiAccess(User user) + { + return user.HasApiAccess == true; + } + + private int? MapOrganizationId(User user, IPrincipal principal) + { + var method = MapAuthenticationMethod(principal); + if (method == AuthenticationMethod.KitosToken) + { + var orgId = (principal.Identity as ClaimsIdentity).GetClaimOrNull(BearerTokenConfig.DefaultOrganizationClaimName); + + if (orgId != null) + { + if (int.TryParse(orgId.Value, out var id)) + { + return id; + } + _logger.Error("Found Claim {claimName}, but could not parse it to an integer", BearerTokenConfig.DefaultOrganizationClaimName); + } + } + else if (method == AuthenticationMethod.Forms) + { + return user.DefaultOrganizationId; + } + return default(int?); + } + + private AuthenticationMethod MapAuthenticationMethod(IPrincipal user) + { + var authenticationMethod = user.Identity.AuthenticationType; + switch (authenticationMethod) + { + case "JWT": + return AuthenticationMethod.KitosToken; + case "Forms": + return AuthenticationMethod.Forms; + default: + _logger.Error("Unknown authentication method {authenticationMethod}", authenticationMethod); + return AuthenticationMethod.Anonymous; + } + } + + private User GetAuthenticatedUser(IPrincipal user) + { + if (user.Identity.IsAuthenticated) + { + var id = GetUserId(user); + if (id.HasValue) + { + return _userRepository.GetById(id.Value); + } + } + + return null; + } + + private int? ParseInteger(string toParse) + { + if (int.TryParse(toParse, out var asInt)) + { + return asInt; + } + _logger.Error("Could not parse to int: {toParse}", toParse); + return null; + } + + private int? GetUserId(IPrincipal user) + { + var userId = user.Identity.Name; + var id = ParseInteger(userId); + return id; + } + } +} \ No newline at end of file diff --git a/Presentation.Web/Infrastructure/Middleware/APIRequestsLoggingMiddleware.cs b/Presentation.Web/Infrastructure/Middleware/APIRequestsLoggingMiddleware.cs new file mode 100644 index 0000000000..7c9ce4f329 --- /dev/null +++ b/Presentation.Web/Infrastructure/Middleware/APIRequestsLoggingMiddleware.cs @@ -0,0 +1,63 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Core.ApplicationServices.Authentication; +using Microsoft.Owin; +using Ninject; +using Serilog; +using Serilog.Context; + +namespace Presentation.Web.Infrastructure.Middleware +{ + public class ApiRequestsLoggingMiddleware : OwinMiddleware + { + private const int INVALID_ID = -1; + public ApiRequestsLoggingMiddleware(OwinMiddleware next) : base(next) + { + } + + public override async Task Invoke(IOwinContext context) + { + var kernel = context.GetNinjectKernel(); + var logger = kernel.Get(); + var authenticationContext = kernel.Get(); + if (authenticationContext.Method == AuthenticationMethod.KitosToken) + { + var guid = Guid.NewGuid(); + var requestStart = DateTime.UtcNow; + var route = context.Request.Path; + var method = context.Request.Method; + var queryParameters = GetQueryParameters(context.Request.Query); + var userId = authenticationContext.UserId.GetValueOrDefault(INVALID_ID); + var loggedIntoOrganizationId = authenticationContext.ActiveOrganizationId.GetValueOrDefault(INVALID_ID); + using (LogContext.PushProperty("CorrelationId", guid.ToString())) + { + logger.Information("Route: {route} Method: {method} QueryParameters: {queryParameters} UserID: {userID} LoggedIntoOrganizationId: {loggedIntoOrganizationId} RequestStartUTC: {requestStart}", route, method, queryParameters, userId, loggedIntoOrganizationId, requestStart); + try + { + await Next.Invoke(context); + } + finally + { + var requestEnd = DateTime.UtcNow; + logger.Information("Route: {route} Method: {method} QueryParameters: {queryParameters} UserID: {userID} LoggedIntoOrganizationId: {loggedIntoOrganizationId} RequestEndUTC: {requestEnd}", route, method, queryParameters, userId, loggedIntoOrganizationId, requestEnd); + } + } + } + else + { + await Next.Invoke(context); + } + } + + private static string GetQueryParameters(IReadableStringCollection query) + { + if (query.Any()) + { + var parameters = query.Select(i => i.Key).Aggregate((i, j) => i + ", " + j); + return parameters; + } + return string.Empty; + } + } +} \ No newline at end of file diff --git a/Presentation.Web/Infrastructure/Middleware/DenyModificationsThroughApiMiddleware.cs b/Presentation.Web/Infrastructure/Middleware/DenyModificationsThroughApiMiddleware.cs new file mode 100644 index 0000000000..234cfcb0ce --- /dev/null +++ b/Presentation.Web/Infrastructure/Middleware/DenyModificationsThroughApiMiddleware.cs @@ -0,0 +1,39 @@ +using System.Threading.Tasks; +using Core.ApplicationServices.Authentication; +using Microsoft.Owin; +using Ninject; +using Presentation.Web.Extensions; +using Serilog; + +namespace Presentation.Web.Infrastructure.Middleware +{ + public class DenyModificationsThroughApiMiddleware : OwinMiddleware + { + public DenyModificationsThroughApiMiddleware(OwinMiddleware next) : base(next) + { + } + + public override async Task Invoke(IOwinContext context) + { + var kernel = context.GetNinjectKernel(); + var logger = kernel.Get(); + var authenticationContext = kernel.Get(); + if (authenticationContext.Method == AuthenticationMethod.KitosToken && IsMutationAttempt(context)) + { + logger.Warning("User with id: {userID} attempted to mutate resource: {url} by method {method}", + authenticationContext.UserId, context.Request.Uri.ToString(), context.Request.Method); + context.Response.StatusCode = 403; + context.Response.Write("Det er ikke tilladt at skrive data via APIet"); + } + else + { + await Next.Invoke(context); + } + } + + private static bool IsMutationAttempt(IOwinContext context) + { + return context.Request.Method.IsMutation(); + } + } +} \ No newline at end of file diff --git a/Presentation.Web/Infrastructure/Middleware/DenyUsersWithoutApiAccessMiddleware.cs b/Presentation.Web/Infrastructure/Middleware/DenyUsersWithoutApiAccessMiddleware.cs new file mode 100644 index 0000000000..c02e6c1362 --- /dev/null +++ b/Presentation.Web/Infrastructure/Middleware/DenyUsersWithoutApiAccessMiddleware.cs @@ -0,0 +1,33 @@ +using System.Threading.Tasks; +using Core.ApplicationServices.Authentication; +using Microsoft.Owin; +using Ninject; +using Serilog; + +namespace Presentation.Web.Infrastructure.Middleware +{ + public class DenyUsersWithoutApiAccessMiddleware : OwinMiddleware + { + public DenyUsersWithoutApiAccessMiddleware(OwinMiddleware next) : base(next) + { + } + + public override async Task Invoke(IOwinContext context) + { + var kernel = context.GetNinjectKernel(); + var logger = kernel.Get(); + var authenticationContext = kernel.Get(); + if (authenticationContext.Method == AuthenticationMethod.KitosToken && !authenticationContext.HasApiAccess) + { + logger.Warning("User with id: {userID} made an API call without having API access", + authenticationContext.UserId); + context.Response.StatusCode = 403; + context.Response.Write("Du har ikke tilladelse til at kalde API endpoints"); + } + else + { + await Next.Invoke(context); + } + } + } +} \ No newline at end of file diff --git a/Presentation.Web/Infrastructure/Middleware/OwinNinjectMiddleware.cs b/Presentation.Web/Infrastructure/Middleware/OwinNinjectMiddleware.cs new file mode 100644 index 0000000000..dd5ab9cfe7 --- /dev/null +++ b/Presentation.Web/Infrastructure/Middleware/OwinNinjectMiddleware.cs @@ -0,0 +1,34 @@ +using Microsoft.Owin; +using Ninject; +using Ninject.Web.Common; +using Owin; +using Presentation.Web.Extensions; + +namespace Presentation.Web.Infrastructure.Middleware +{ + public static class OwinNinjectMiddleware + { + public static IAppBuilder UseNinject(this IAppBuilder app) + { + return app.Use(async (context, next) => + { + //Add Ninject to the following middlewares + AddNinjectKernel(context); + + await next(); + }); + } + + private static void AddNinjectKernel(IOwinContext context) + { + //Bootstrapper holds a singleton reference to the root kernel accessed through an instance property. + context.WithEnvironmentProperty(new Bootstrapper().Kernel); + } + + public static IKernel GetNinjectKernel(this IOwinContext context) + { + return context.GetEnvironmentProperty(); + } + + } +} \ No newline at end of file diff --git a/Presentation.Web/Infrastructure/Model/Authentication/BearerTokenConfig.cs b/Presentation.Web/Infrastructure/Model/Authentication/BearerTokenConfig.cs new file mode 100644 index 0000000000..7e2362841e --- /dev/null +++ b/Presentation.Web/Infrastructure/Model/Authentication/BearerTokenConfig.cs @@ -0,0 +1,19 @@ +using System.IdentityModel.Tokens; +using System.Text; +using Presentation.Web.Properties; + +namespace Presentation.Web.Infrastructure.Model.Authentication +{ + public class BearerTokenConfig + { + public const string DefaultOrganizationClaimName = "DefaultOrganization"; + public static string Issuer => Settings.Default.BaseUrl; + + public static InMemorySymmetricSecurityKey SecurityKey => + new InMemorySymmetricSecurityKey( + Encoding.UTF8.GetBytes( + System.Web.Configuration.WebConfigurationManager.AppSettings["SecurityKeyString"] + ) + ); + } +} \ No newline at end of file diff --git a/Presentation.Web/Infrastructure/Model/Authentication/KitosApiToken.cs b/Presentation.Web/Infrastructure/Model/Authentication/KitosApiToken.cs new file mode 100644 index 0000000000..1238d5233e --- /dev/null +++ b/Presentation.Web/Infrastructure/Model/Authentication/KitosApiToken.cs @@ -0,0 +1,19 @@ +using System; +using Core.DomainModel; + +namespace Presentation.Web.Infrastructure.Model.Authentication +{ + public class KitosApiToken + { + public User User { get; } + public string Value { get; } + public DateTime Expiration { get; } + + public KitosApiToken(User user, string value, DateTime expiration) + { + User = user; + Value = value; + Expiration = expiration; + } + } +} \ No newline at end of file diff --git a/Presentation.Web/Infrastructure/Model/Authentication/SsoConfig.cs b/Presentation.Web/Infrastructure/Model/Authentication/SsoConfig.cs new file mode 100644 index 0000000000..be38f4610a --- /dev/null +++ b/Presentation.Web/Infrastructure/Model/Authentication/SsoConfig.cs @@ -0,0 +1,11 @@ +using System.IdentityModel.Tokens; + +namespace Presentation.Web.Infrastructure.Model.Authentication +{ + public class SsoConfig + { + public SecurityKey SigningKey { get; set; } + public string Issuer { get; set; } + public string Audience { get; set; } + } +} \ No newline at end of file diff --git a/Presentation.Web/Infrastructure/Odata/CaseInsensitiveResolver.cs b/Presentation.Web/Infrastructure/Odata/CaseInsensitiveResolver.cs new file mode 100644 index 0000000000..b47cc5d984 --- /dev/null +++ b/Presentation.Web/Infrastructure/Odata/CaseInsensitiveResolver.cs @@ -0,0 +1,14 @@ +using Microsoft.OData.UriParser; + +namespace Presentation.Web.Infrastructure.Odata +{ + //For making urls case insensitive + internal class CaseInsensitiveResolver : ODataUriResolver + { + public override bool EnableCaseInsensitive + { + get => true; + set { /*ignore - always return true*/ } + } + } +} \ No newline at end of file diff --git a/Presentation.Web/Infrastructure/TokenValidator.cs b/Presentation.Web/Infrastructure/TokenValidator.cs index 7b9105234c..29ec31b670 100644 --- a/Presentation.Web/Infrastructure/TokenValidator.cs +++ b/Presentation.Web/Infrastructure/TokenValidator.cs @@ -1,17 +1,15 @@ using System; -using System.Collections.Generic; using System.Configuration; -using System.IO; using System.Linq; using System.Net; using System.Security.Claims; -using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; -using System.Web; -using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Serilog; +using System.IdentityModel.Tokens; +using System.Security.Principal; +using Presentation.Web.Infrastructure.Model.Authentication; namespace Presentation.Web.Infrastructure { @@ -28,7 +26,7 @@ public ClaimsPrincipal Validate(string idToken) Logger.Error("TokenValidator: Could not load SSOConfig"); return null; } - var tokenhandler = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler(); + var tokenhandler = new JwtSecurityTokenHandler(); SecurityToken sToken; var tokenValidationParameters = new TokenValidationParameters { @@ -45,7 +43,44 @@ public ClaimsPrincipal Validate(string idToken) } } - private SsoConfig GetKeyFromConfig() + public KitosApiToken CreateToken(Core.DomainModel.User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + var handler = new JwtSecurityTokenHandler(); + + var identity = new ClaimsIdentity(new GenericIdentity(user.Id.ToString(), "TokenAuth")); + if (user.DefaultOrganizationId.HasValue) + { + identity.AddClaim(new Claim(BearerTokenConfig.DefaultOrganizationClaimName, user.DefaultOrganizationId.Value.ToString("D"))); + } + + // securityKey length should be >256b + try + { + var validFrom = DateTime.UtcNow; + var expires = validFrom.AddDays(1); + var securityToken = handler.CreateToken(new SecurityTokenDescriptor + { + Subject = identity, + TokenIssuerName = BearerTokenConfig.Issuer, + Lifetime = new System.IdentityModel.Protocols.WSTrust.Lifetime(validFrom, expires), + SigningCredentials = new SigningCredentials(BearerTokenConfig.SecurityKey, SecurityAlgorithms.HmacSha256Signature, SecurityAlgorithms.Sha256Digest) + }); + var tokenString = handler.WriteToken(securityToken); + return new KitosApiToken(user, tokenString, expires); + } + catch (Exception exn) + { + Logger.Error(exn, "TokenValidator: Exception creating token."); + throw; + } + } + + public SsoConfig GetKeyFromConfig() { var result = new SsoConfig(); var configUrl = ConfigurationManager.AppSettings["SSOGateway"]; @@ -58,10 +93,10 @@ private SsoConfig GetKeyFromConfig() var openidConfig = JsonConvert.DeserializeObject(json); result.Issuer = openidConfig.issuer; - var jwksuri = (string) openidConfig.jwks_uri; + var jwksuri = (string)openidConfig.jwks_uri; var jwksjson = wc.DownloadString(jwksuri); var jwks = JsonConvert.DeserializeObject(jwksjson); - var keys = (JArray) jwks.keys; + var keys = (JArray)jwks.keys; var cert = keys.First.Single(t => t.Path.Contains("x5c")).First.First.ToString(); result.SigningKey = new X509SecurityKey(new X509Certificate2(Convert.FromBase64String(cert))); } @@ -74,11 +109,4 @@ private SsoConfig GetKeyFromConfig() return result; } } - - internal class SsoConfig - { - public SecurityKey SigningKey { get; set; } - public string Issuer { get; set; } - public string Audience { get; set; } - } } \ No newline at end of file diff --git a/Presentation.Web/Models/AdviceUserRelationDTO.cs b/Presentation.Web/Models/AdviceUserRelationDTO.cs index 2ecc3903bc..df22ec3e7f 100644 --- a/Presentation.Web/Models/AdviceUserRelationDTO.cs +++ b/Presentation.Web/Models/AdviceUserRelationDTO.cs @@ -1,8 +1,4 @@ using Core.DomainModel.Advice; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; namespace Presentation.Web.Models { diff --git a/Presentation.Web/Models/ArchivePeriodDTO.cs b/Presentation.Web/Models/ArchivePeriodDTO.cs index 33ad8c77e7..c48437d005 100644 --- a/Presentation.Web/Models/ArchivePeriodDTO.cs +++ b/Presentation.Web/Models/ArchivePeriodDTO.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; namespace Presentation.Web.Models { diff --git a/Presentation.Web/Models/ContactPersonDTO.cs b/Presentation.Web/Models/ContactPersonDTO.cs index b15b13edc7..0949372b82 100644 --- a/Presentation.Web/Models/ContactPersonDTO.cs +++ b/Presentation.Web/Models/ContactPersonDTO.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; - -namespace Presentation.Web.Models +namespace Presentation.Web.Models { public class ContactPersonDTO { diff --git a/Presentation.Web/Models/DataProtectionAdvisorDTO.cs b/Presentation.Web/Models/DataProtectionAdvisorDTO.cs index fb113be29c..c4d577efc9 100644 --- a/Presentation.Web/Models/DataProtectionAdvisorDTO.cs +++ b/Presentation.Web/Models/DataProtectionAdvisorDTO.cs @@ -1,6 +1,4 @@ -using System; - -namespace Presentation.Web.Models +namespace Presentation.Web.Models { public class DataProtectionAdvisorDTO { diff --git a/Presentation.Web/Models/DataResponsibleDTO.cs b/Presentation.Web/Models/DataResponsibleDTO.cs index 68bfc5e17f..86ec88f109 100644 --- a/Presentation.Web/Models/DataResponsibleDTO.cs +++ b/Presentation.Web/Models/DataResponsibleDTO.cs @@ -1,6 +1,4 @@ -using System; - -namespace Presentation.Web.Models +namespace Presentation.Web.Models { public class DataResponsibleDTO { diff --git a/Presentation.Web/Models/EntitiesAccessRightsDTO.cs b/Presentation.Web/Models/EntitiesAccessRightsDTO.cs new file mode 100644 index 0000000000..1578cc88b4 --- /dev/null +++ b/Presentation.Web/Models/EntitiesAccessRightsDTO.cs @@ -0,0 +1,8 @@ +namespace Presentation.Web.Models +{ + public class EntitiesAccessRightsDTO + { + public bool CanCreate { get; set; } + public bool CanView { get; set; } + } +} \ No newline at end of file diff --git a/Presentation.Web/Models/EntityAccessRightsDTO.cs b/Presentation.Web/Models/EntityAccessRightsDTO.cs new file mode 100644 index 0000000000..068ff9f400 --- /dev/null +++ b/Presentation.Web/Models/EntityAccessRightsDTO.cs @@ -0,0 +1,9 @@ +namespace Presentation.Web.Models +{ + public class EntityAccessRightsDTO + { + public bool CanEdit { get; set; } + public bool CanDelete { get; set; } + public bool CanView { get; set; } + } +} \ No newline at end of file diff --git a/Presentation.Web/Models/ExternalReferenceDTO.cs b/Presentation.Web/Models/ExternalReferenceDTO.cs index 4dc06ed25a..c03a327cf1 100644 --- a/Presentation.Web/Models/ExternalReferenceDTO.cs +++ b/Presentation.Web/Models/ExternalReferenceDTO.cs @@ -1,8 +1,4 @@ -using Core.DomainModel.ItProject; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; +using System; namespace Presentation.Web.Models { diff --git a/Presentation.Web/Models/GetTokenResponseDTO.cs b/Presentation.Web/Models/GetTokenResponseDTO.cs new file mode 100644 index 0000000000..0a8e8e22d4 --- /dev/null +++ b/Presentation.Web/Models/GetTokenResponseDTO.cs @@ -0,0 +1,12 @@ +using System; + +namespace Presentation.Web.Models +{ + public class GetTokenResponseDTO + { + public string Token { get; set; } + public string Email { get; set; } + public bool LoginSuccessful { get; set; } + public DateTime Expires { get; set; } + } +} \ No newline at end of file diff --git a/Presentation.Web/Models/ItContractDTO.cs b/Presentation.Web/Models/ItContractDTO.cs index cdab32d322..44f72015a7 100644 --- a/Presentation.Web/Models/ItContractDTO.cs +++ b/Presentation.Web/Models/ItContractDTO.cs @@ -1,5 +1,4 @@ -using Core.DomainModel; -using Core.DomainModel.ItSystem.DataTypes; +using Core.DomainModel.ItSystem.DataTypes; using System; using System.Collections.Generic; diff --git a/Presentation.Web/Models/ItInterfaceExhibitUsageDTO.cs b/Presentation.Web/Models/ItInterfaceExhibitUsageDTO.cs index 009a2229c1..e81252dd3b 100644 --- a/Presentation.Web/Models/ItInterfaceExhibitUsageDTO.cs +++ b/Presentation.Web/Models/ItInterfaceExhibitUsageDTO.cs @@ -10,7 +10,6 @@ public class ItInterfaceExhibitUsageDTO public int? ItContractId { get; set; } public ItContractSystemDTO ItContract { get; set; } public bool IsWishedFor { get; set; } - //public int ItInterfaceExhibitItSystemId { get; set; } public string ItInterfaceExhibitItSystemName { get; set; } public bool ItInterfaceExhibitItInterfaceDisabled { get; set; } } diff --git a/Presentation.Web/Models/ItSystemDTO.cs b/Presentation.Web/Models/ItSystemDTO.cs index 14dcf669b4..a81cff5023 100644 --- a/Presentation.Web/Models/ItSystemDTO.cs +++ b/Presentation.Web/Models/ItSystemDTO.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using Core.DomainModel; -using Core.DomainModel.ItSystem; using Core.DomainModel.ItSystem.DataTypes; namespace Presentation.Web.Models diff --git a/Presentation.Web/Models/ItSystemDataWorkerRelationDTO.cs b/Presentation.Web/Models/ItSystemDataWorkerRelationDTO.cs index 757dc189bf..d1b0ae0201 100644 --- a/Presentation.Web/Models/ItSystemDataWorkerRelationDTO.cs +++ b/Presentation.Web/Models/ItSystemDataWorkerRelationDTO.cs @@ -1,11 +1,4 @@ -using Core.DomainModel.ItSystem; -using Core.DomainModel.Organization; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; - -namespace Presentation.Web.Models +namespace Presentation.Web.Models { public class ItSystemDataWorkerRelationDTO { diff --git a/Presentation.Web/Models/ItSystemUsageDataWorkerRelationDTO.cs b/Presentation.Web/Models/ItSystemUsageDataWorkerRelationDTO.cs index 6567975b65..5a8693d990 100644 --- a/Presentation.Web/Models/ItSystemUsageDataWorkerRelationDTO.cs +++ b/Presentation.Web/Models/ItSystemUsageDataWorkerRelationDTO.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Core.DomainModel.ItSystem +namespace Core.DomainModel.ItSystem { public class ItSystemUsageDataWorkerRelationDTO { diff --git a/Presentation.Web/Models/OrganizationSimpleDTO.cs b/Presentation.Web/Models/OrganizationSimpleDTO.cs index 25d09f6ac4..bf4e2e3c31 100644 --- a/Presentation.Web/Models/OrganizationSimpleDTO.cs +++ b/Presentation.Web/Models/OrganizationSimpleDTO.cs @@ -1,7 +1,4 @@ -using System; -using Core.DomainModel; - -namespace Presentation.Web.Models +namespace Presentation.Web.Models { public class OrganizationSimpleDTO { diff --git a/Presentation.Web/Models/PagingModel.cs b/Presentation.Web/Models/PagingModel.cs index 7fa81b0e99..26006f2110 100644 --- a/Presentation.Web/Models/PagingModel.cs +++ b/Presentation.Web/Models/PagingModel.cs @@ -9,7 +9,8 @@ public class PagingModel { public PagingModel() { - _filters = new List>>(); + _dbFilters = new List>>(); + _postProcessingFilters = new List>(); Skip = 0; Take = 100; OrderBy = "Id"; @@ -21,22 +22,52 @@ public PagingModel() public string OrderBy { get; set; } public bool Descending { get; set; } - private readonly List>> _filters; + private readonly List>> _dbFilters; + private readonly List> _postProcessingFilters; public PagingModel Where(Expression> filter) { - _filters.Add(filter); + _dbFilters.Add(filter); + return this; + } + + /// + /// Add post-processing filter, which can be applied to in-memory objects before applying the paging. + /// + /// + /// + public PagingModel WithPostProcessingFilter(Predicate filter) + { + _postProcessingFilters.Add(filter); return this; } public IQueryable Filter(IQueryable query) { - foreach (var filter in _filters) + foreach (var filter in _dbFilters) { query = query.Where(filter); } return query; } + + /// + /// Applies pre-paging processing of data queried by main filters. + /// + /// + /// + public IQueryable ApplyPostProcessing(IQueryable content) + { + if (_postProcessingFilters.Any()) + { + return content + .AsEnumerable() + .Where(x => _postProcessingFilters.ToList().Any(filter => filter(x) == false) == false) + .AsQueryable(); + } + + return content; + } } } diff --git a/Presentation.Web/Models/ReportItSystemRightOutputDTO.cs b/Presentation.Web/Models/ReportItSystemRightOutputDTO.cs index 58d3a529c0..251df179eb 100644 --- a/Presentation.Web/Models/ReportItSystemRightOutputDTO.cs +++ b/Presentation.Web/Models/ReportItSystemRightOutputDTO.cs @@ -1,6 +1,4 @@ -using Core.DomainModel; -using Core.DomainModel.ItSystem; -using System; +using System; namespace Presentation.Web.Models { diff --git a/Presentation.Web/Models/SSOConfigDTO.cs b/Presentation.Web/Models/SSOConfigDTO.cs new file mode 100644 index 0000000000..c1ab2b928d --- /dev/null +++ b/Presentation.Web/Models/SSOConfigDTO.cs @@ -0,0 +1,9 @@ +namespace Presentation.Web.Models +{ + public class SSOConfigDTO + { + public string SSOGateway { get; set; } + + public string SSOAudience { get; set; } + } +} \ No newline at end of file diff --git a/Presentation.Web/Models/UserDTO.cs b/Presentation.Web/Models/UserDTO.cs index 9fcd0c8ced..a2671cdc96 100644 --- a/Presentation.Web/Models/UserDTO.cs +++ b/Presentation.Web/Models/UserDTO.cs @@ -21,6 +21,7 @@ public class UserDTO public DateTime? LastAdvisDate { get; set; } public DateTime LastChanged { get; set; } public int? LastChangedByUserId { get; set; } + public bool? HasApiAccess { get; set; } public string FullName { diff --git a/Presentation.Web/Parameters.xml b/Presentation.Web/Parameters.xml index 977f9f1b9b..e57ebbce92 100644 --- a/Presentation.Web/Parameters.xml +++ b/Presentation.Web/Parameters.xml @@ -17,6 +17,12 @@ + + + + + + @@ -55,4 +61,15 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Presentation.Web/Presentation.Web.csproj b/Presentation.Web/Presentation.Web.csproj index e17446ebbd..a2c7ce60c7 100644 --- a/Presentation.Web/Presentation.Web.csproj +++ b/Presentation.Web/Presentation.Web.csproj @@ -43,8 +43,7 @@ DEBUG;TRACE prompt 0 - - + bin\Presentation.Web.xml AllRules.ruleset false @@ -56,6 +55,7 @@ prompt 4 OnlyFilesToRunTheApp + bin\Presentation.Web.xml @@ -99,36 +99,65 @@ True - - ..\packages\Microsoft.IdentityModel.Logging.1.1.0\lib\net451\Microsoft.IdentityModel.Logging.dll + + ..\packages\Microsoft.Data.Edm.5.8.4\lib\net40\Microsoft.Data.Edm.dll + + + ..\packages\Microsoft.Data.OData.5.8.4\lib\net40\Microsoft.Data.OData.dll + + + ..\packages\Microsoft.Data.Services.5.8.4\lib\net40\Microsoft.Data.Services.dll + + + ..\packages\Microsoft.Data.Services.Client.5.8.4\lib\net40\Microsoft.Data.Services.Client.dll + + + ..\packages\Microsoft.Extensions.DependencyInjection.1.0.0\lib\netstandard1.1\Microsoft.Extensions.DependencyInjection.dll True - - ..\packages\Microsoft.IdentityModel.Tokens.5.1.0\lib\net451\Microsoft.IdentityModel.Tokens.dll + + ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.0.0\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll True - - ..\packages\Microsoft.OData.Core.6.15.0\lib\portable-net45+win+wpa81\Microsoft.OData.Core.dll + + ..\packages\Microsoft.IdentityModel.Logging.5.2.1\lib\net451\Microsoft.IdentityModel.Logging.dll True - - ..\packages\Microsoft.OData.Edm.6.15.0\lib\portable-net45+win+wpa81\Microsoft.OData.Edm.dll + + ..\packages\Microsoft.IdentityModel.Tokens.5.2.1\lib\net451\Microsoft.IdentityModel.Tokens.dll True - - ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + + ..\packages\Microsoft.OData.Core.7.5.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Core.dll + + + ..\packages\Microsoft.OData.Edm.7.5.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Edm.dll + + + ..\packages\Microsoft.Owin.4.0.0\lib\net451\Microsoft.Owin.dll True ..\packages\Microsoft.Owin.Host.SystemWeb.3.0.0\lib\net45\Microsoft.Owin.Host.SystemWeb.dll True - - ..\packages\Microsoft.Spatial.6.15.0\lib\portable-net45+win+wpa81\Microsoft.Spatial.dll + + ..\packages\Microsoft.Owin.Security.4.0.0\lib\net451\Microsoft.Owin.Security.dll + True + + + ..\packages\Microsoft.Owin.Security.Jwt.3.1.0\lib\net45\Microsoft.Owin.Security.Jwt.dll True - - ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + + ..\packages\Microsoft.Owin.Security.OAuth.4.0.0\lib\net451\Microsoft.Owin.Security.OAuth.dll + True + + + ..\packages\Microsoft.Spatial.7.5.0\lib\portable-net45+win8+wpa81\Microsoft.Spatial.dll + + + ..\packages\Newtonsoft.Json.10.0.1\lib\net45\Newtonsoft.Json.dll True @@ -186,18 +215,29 @@ externalreferences\SerilogWeb.Classic.dll + + ..\packages\Swashbuckle.Core.5.5.2\lib\net40\Swashbuckle.Core.dll + + + ..\packages\Swashbuckle.OData.3.5.0\lib\net452\Swashbuckle.OData.dll + - - ..\packages\System.IdentityModel.Tokens.Jwt.5.1.0\lib\net451\System.IdentityModel.Tokens.Jwt.dll + + + ..\packages\System.IdentityModel.Tokens.Jwt.4.0.2.202250711\lib\net45\System.IdentityModel.Tokens.Jwt.dll True ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll + + + ..\packages\System.Spatial.5.8.4\lib\net40\System.Spatial.dll + ..\packages\Microsoft.AspNet.Cors.5.2.3\lib\net45\System.Web.Cors.dll True @@ -228,9 +268,8 @@ - - ..\packages\Microsoft.AspNet.OData.5.9.1\lib\net45\System.Web.OData.dll - True + + ..\packages\Microsoft.AspNet.OData.6.0.0\lib\net45\System.Web.OData.dll ..\packages\Microsoft.AspNet.Web.Optimization.1.1.3\lib\net40\System.Web.Optimization.dll @@ -280,6 +319,14 @@ + + + + + + + + @@ -304,6 +351,7 @@ + @@ -332,59 +380,43 @@ - - - + - + + + + + - + + - + - - - - + + + + + + + + + + + + + + @@ -393,11 +425,12 @@ + - - + + @@ -406,88 +439,91 @@ + + + - - - - + - - + + - - - - - - - + - - - - - - - - - - - - + + + + - - - - - + + + + + + + - + + + + + + + + + + - + + + + + - - + + + + @@ -635,15 +671,20 @@ + - + + + - + + + @@ -885,7 +926,6 @@ - @@ -1175,17 +1215,7 @@ - - - Designer - - - Designer - - - Designer - SettingsSingleFileGenerator Settings.Designer.cs @@ -1200,23 +1230,14 @@ + Designer - - Web.config - - - Web.config - True - - Web.config - Designer - - Web.config True + Designer @@ -1263,6 +1284,10 @@ {5AE358F5-4F11-47FE-94BE-1588F357DCDC} Infrastructure.OpenXML + + {0326CAE6-87A1-4D66-84AE-EB8CE0340E9F} + Infrastructure.Services + @@ -1272,6 +1297,7 @@ + @@ -1291,24 +1317,6 @@ 10.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - bin\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - @@ -1322,9 +1330,6 @@ commonjs - diff --git a/Presentation.Web/Properties/PublishProfiles/DeployToProd.pubxml b/Presentation.Web/Properties/PublishProfiles/DeployToProd.pubxml deleted file mode 100644 index a20ec7cc77..0000000000 --- a/Presentation.Web/Properties/PublishProfiles/DeployToProd.pubxml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - MSDeploy - False - Prod - Any CPU - https://kitos.dk - True - False - https://10.7.23.10:8172/msdeploy.axd - Default Web Site - - True - WMSVC - True - KITOS-PROD-WEB\KITOSADMIN - <_SavePWD>True - - - - - - - - - - - - - - - - - - - - - - - - False - - - False - - - \ No newline at end of file diff --git a/Presentation.Web/Properties/PublishProfiles/DeployToTest.pubxml b/Presentation.Web/Properties/PublishProfiles/DeployToTest.pubxml deleted file mode 100644 index 6294b72040..0000000000 --- a/Presentation.Web/Properties/PublishProfiles/DeployToTest.pubxml +++ /dev/null @@ -1,56 +0,0 @@ - - - - - MSDeploy - False - Test - Any CPU - https://kitostest.miracle.dk - True - False - http://10.7.4.93 - Default Web Site - - False - RemoteAgent - True - deploy - <_SavePWD>True - - - - - - - - - - - - - - - - - - - - - True - True - False - DonotMerge - - - - False - - - False - - - \ No newline at end of file diff --git a/Presentation.Web/Properties/PublishProfiles/Kitos - Pre-prod (sandbox).pubxml b/Presentation.Web/Properties/PublishProfiles/Kitos - Pre-prod (sandbox).pubxml deleted file mode 100644 index fe1e941a4c..0000000000 --- a/Presentation.Web/Properties/PublishProfiles/Kitos - Pre-prod (sandbox).pubxml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - MSDeploy - Kitos - Sandbox - Any CPU - - True - True - 91.236.123.172 - Default Web Site - - False - WMSVC - True - <_SavePWD>True - - - - - - - - - - - False - - - - False - - - \ No newline at end of file diff --git a/Presentation.Web/Properties/PublishProfiles/Local.pubxml b/Presentation.Web/Properties/PublishProfiles/Local.pubxml deleted file mode 100644 index 5cc7029e12..0000000000 --- a/Presentation.Web/Properties/PublishProfiles/Local.pubxml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - FileSystem - Debug - Any CPU - - True - False - C:\Slet\Publish - True - - \ No newline at end of file diff --git a/Presentation.Web/Properties/PublishProfiles/MiracleTest.pubxml b/Presentation.Web/Properties/PublishProfiles/MiracleTest.pubxml deleted file mode 100644 index ab4058267b..0000000000 --- a/Presentation.Web/Properties/PublishProfiles/MiracleTest.pubxml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - MSDeploy - Debug - Any CPU - https://kitostest.miracle.dk - True - False - http://10.7.4.93 - Default Web Site - - False - RemoteAgent - True - \deploy - <_SavePWD>True - - - - - - - - - - - - - - - - - - - - - False - - - - Server=.\SQLEXPRESS;User ID=kitos;Password=kitos;Initial Catalog=kitos_new;MultipleActiveResultSets=True - False - - - - \ No newline at end of file diff --git a/Presentation.Web/Properties/Settings.Designer.cs b/Presentation.Web/Properties/Settings.Designer.cs index 86c8a2a571..5491c7b6aa 100644 --- a/Presentation.Web/Properties/Settings.Designer.cs +++ b/Presentation.Web/Properties/Settings.Designer.cs @@ -67,5 +67,23 @@ public string DeploymentVersion { return ((string)(this["DeploymentVersion"])); } } + + [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("arne123")] + public string DefaultUserPassword { + get { + return ((string)(this["DefaultUserPassword"])); + } + } + + [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("true")] + public string UseDefaultPassword { + get { + return ((string)(this["UseDefaultPassword"])); + } + } } } diff --git a/Presentation.Web/Properties/Settings.settings b/Presentation.Web/Properties/Settings.settings index f4cafbfc11..c30531d1aa 100644 --- a/Presentation.Web/Properties/Settings.settings +++ b/Presentation.Web/Properties/Settings.settings @@ -17,5 +17,11 @@ unknown + + arne123 + + + true + \ No newline at end of file diff --git a/Presentation.Web/Scripts/SwaggerUICustom.js b/Presentation.Web/Scripts/SwaggerUICustom.js new file mode 100644 index 0000000000..d316680e9e --- /dev/null +++ b/Presentation.Web/Scripts/SwaggerUICustom.js @@ -0,0 +1,11 @@ +(function () { + function addApiKeyAuthorization() { + var key = $('#input_apiKey')[0].value; + if (key && key.trim() != "") { + var apiKeyAuth = new SwaggerClient.ApiKeyAuthorization(swashbuckleConfig.apiKeyName, key, swashbuckleConfig.apiKeyIn); + window.swaggerUi.api.clientAuthorizations.add("api_key", apiKeyAuth); + console.log("added key " + key); + } + } + $('#input_apiKey').change(addApiKeyAuthorization); +})(); \ No newline at end of file diff --git a/Presentation.Web/Startup.cs b/Presentation.Web/Startup.cs index a4dddeb84f..3edb3e49b6 100644 --- a/Presentation.Web/Startup.cs +++ b/Presentation.Web/Startup.cs @@ -1,20 +1,43 @@ using Microsoft.Owin; using Owin; using Hangfire; -[assembly: OwinStartup(typeof(Presentation.Web.Startup))] +using System.IdentityModel.Tokens; +using Presentation.Web.Infrastructure.Middleware; +using Presentation.Web.Infrastructure.Model.Authentication; +[assembly: OwinStartup(typeof(Presentation.Web.Startup))] namespace Presentation.Web { public class Startup { public void Configuration(IAppBuilder app) { - // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888 - // Initializing the Hangfire scheduler GlobalConfiguration.Configuration.UseSqlServerStorage("kitos_HangfireDB"); app.UseHangfireDashboard(); app.UseHangfireServer(); + + //setup token authentication + app.UseJwtBearerAuthentication(new Microsoft.Owin.Security.Jwt.JwtBearerAuthenticationOptions + { + AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active, + TokenValidationParameters = new TokenValidationParameters + { + ValidateAudience = false, + ValidIssuer = BearerTokenConfig.Issuer, + ValidateIssuer = true, + + IssuerSigningKey = BearerTokenConfig.SecurityKey, + ValidateIssuerSigningKey = true, + + ValidateLifetime = true, + } + }); + + app.UseNinject(); + app.Use(); + app.Use(); + app.Use(); } } } diff --git a/Presentation.Web/Swagger/RemoveInternalApiOperationsFilter.cs b/Presentation.Web/Swagger/RemoveInternalApiOperationsFilter.cs new file mode 100644 index 0000000000..b0a074510d --- /dev/null +++ b/Presentation.Web/Swagger/RemoveInternalApiOperationsFilter.cs @@ -0,0 +1,40 @@ +using System.Linq; +using System.Web.Http.Controllers; +using System.Web.Http.Description; +using Presentation.Web.Extensions; +using Presentation.Web.Infrastructure.Attributes; +using Swashbuckle.Swagger; + +namespace Presentation.Web.Swagger +{ + public class RemoveInternalApiOperationsFilter : IDocumentFilter + { + public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, IApiExplorer apiExplorer) + { + foreach (var apiDescription in apiExplorer.ApiDescriptions) + { + if (IsControllerInternal(apiDescription) || IsActionInternal(apiDescription)) + { + var route = "/" + apiDescription.RelativePath.TrimEnd('/'); + + swaggerDoc.paths.Remove(route); + } + } + } + + private static bool IsActionInternal(ApiDescription apiDescription) + { + return apiDescription.ActionDescriptor.GetCustomAttributes().Any(); + } + + private static bool IsControllerInternal(ApiDescription apiDescription) + { + return apiDescription.ActionDescriptor.ControllerDescriptor.GetCustomAttributes().Any(); + } + + private static bool IsMutation(HttpActionDescriptor actionDescriptor) + { + return actionDescriptor.SupportedHttpMethods.Any(x => x.Method.IsMutation()); + } + } +} \ No newline at end of file diff --git a/Presentation.Web/Swagger/RemoveMutatingCallsFilter.cs b/Presentation.Web/Swagger/RemoveMutatingCallsFilter.cs new file mode 100644 index 0000000000..36bb0aa274 --- /dev/null +++ b/Presentation.Web/Swagger/RemoveMutatingCallsFilter.cs @@ -0,0 +1,20 @@ +using System.Web.Http.Description; +using Swashbuckle.Swagger; + +namespace Presentation.Web.Swagger +{ + public class RemoveMutatingCallsFilter : IDocumentFilter + { + public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, IApiExplorer apiExplorer) + { + foreach (var swaggerDocPath in swaggerDoc.paths) + { + var pathItem = swaggerDocPath.Value; + pathItem.delete = null; + pathItem.post = null; + pathItem.patch = null; + pathItem.put = null; + } + } + } +} \ No newline at end of file diff --git a/Presentation.Web/Tests/Helpers/CatalogHelper.ts b/Presentation.Web/Tests/Helpers/CatalogHelper.ts index 4967a44c4f..412c962725 100644 --- a/Presentation.Web/Tests/Helpers/CatalogHelper.ts +++ b/Presentation.Web/Tests/Helpers/CatalogHelper.ts @@ -11,23 +11,48 @@ var systemPage = new SystemPage(); var waitUpTo = new WaitTimers(); class CatalogHelper { - public static createCatalog(name: string) { - pageObject.getPage(); - pageObject.kendoToolbarWrapper.headerButtons().systemCatalogCreate.click(); - browser.wait(pageObject.isCreateCatalogAvailable(), waitUpTo.twentySeconds); - element(cssHelper.byDataElementType(consts.nameOfSystemInput)).sendKeys(name); - element(cssHelper.byDataElementType(consts.saveCatalogButton)).click(); + return pageObject.getPage() + .then(() => { + return pageObject.kendoToolbarWrapper.headerButtons().systemCatalogCreate.click(); + }) + .then(() => { + return browser.wait(pageObject.isCreateCatalogAvailable(), waitUpTo.twentySeconds); + }) + .then(() => { + return element(cssHelper.byDataElementType(consts.nameOfSystemInput)).sendKeys(name); + }) + .then(() => { + return element(cssHelper.byDataElementType(consts.saveCatalogButton)).click(); + }); } public static deleteCatalog(name: string) { - pageObject.getPage(); - browser.wait(pageObject.waitForKendoGrid(), waitUpTo.twentySeconds); - pageObject.kendoToolbarWrapper.getFilteredColumnElement(pageObject.kendoToolbarWrapper.columnObjects().catalogName, name).first().click(); - browser.wait(systemPage.isDeleteButtonLoaded(), waitUpTo.twentySeconds); - systemPage.getDeleteButton().click().then(() => { - browser.switchTo().alert().accept(); - }); + return pageObject.getPage() + .then(() => { + return this.waitForKendoGrid(); + }) + .then(() => { + return this.findCatalogColumnsFor(name).first().click(); + }) + .then(() => { + return browser.wait(systemPage.isDeleteButtonLoaded(), waitUpTo.twentySeconds); + }) + .then(() => { + return systemPage.getDeleteButton().click(); + }) + .then(() => { + return browser.switchTo().alert().accept(); + }); + } + + public static findCatalogColumnsFor(name: string) { + return pageObject.kendoToolbarWrapper.getFilteredColumnElement(pageObject.kendoToolbarWrapper.columnObjects().catalogName, name); + } + + public static waitForKendoGrid() { + console.log("Waiting for kendo grid to be ready"); + return browser.wait(pageObject.waitForKendoGrid(), waitUpTo.twentySeconds); } } diff --git a/Presentation.Web/Tests/Helpers/CreateUserHelper.ts b/Presentation.Web/Tests/Helpers/CreateUserHelper.ts new file mode 100644 index 0000000000..22d63b2fb8 --- /dev/null +++ b/Presentation.Web/Tests/Helpers/CreateUserHelper.ts @@ -0,0 +1,62 @@ +import HomePageObjects = require("../PageObjects/Organization/UsersPage.po"); +import CreatePage = require("../PageObjects/Organization/CreateUserPage.po"); +import CSSLocator = require("../object-wrappers/CSSLocatorHelper"); + +class CreateUserHelper { + private cssHelper = new CSSLocator(); + private pageCreateObject = new CreatePage(); + private pageObject = new HomePageObjects(); + public checkApiRoleStatusOnUser(email: string, apiStatus: boolean) { + return this.openEditUser(email) + .then(() => { + var expectedValue = apiStatus ? "true" : null; + return expect(this.pageObject.hasAPiCheckBox.getAttribute("checked")).toEqual(expectedValue); + }); + } + + public updateApiOnUser(email: string, apiAccess: boolean) { + return this.openEditUser(email) + .then(() => { + return this.pageObject.hasAPiCheckBox.isSelected() + .then(selected => { + if (selected !== apiAccess) { + return this.pageCreateObject.boolApi.click() + .then(() => { + return this.pageCreateObject.editUserButton.click(); + }); + } else { + return this.pageCreateObject.cancelEditUserButton.click(); + } + }); + }); + } + + private getUserRow(email: string) { + const emailColumnElementType = "userEmailObject"; + + var rows = this.pageObject.mainGridAllTableRows.filter((row, index) => { + console.log("Searching for email column"); + var column = row.element(this.cssHelper.byDataElementType(emailColumnElementType)); + return column.isPresent() + .then(present => { + if (present) { + console.log("Found email column - checking if row is the right one"); + return column.getText() + .then(text => { + return text === email; + }); + } + return false; + }); + }); + + return rows.first(); + } + + private openEditUser(email: string) { + const row = this.getUserRow(email); + expect(row).not.toBe(null); + return row.element(by.linkText("Redigér")).click(); + } +} +export = CreateUserHelper; \ No newline at end of file diff --git a/Presentation.Web/Tests/Helpers/LoginHelper.ts b/Presentation.Web/Tests/Helpers/LoginHelper.ts index c685ace1f1..6bf5492bc2 100644 --- a/Presentation.Web/Tests/Helpers/LoginHelper.ts +++ b/Presentation.Web/Tests/Helpers/LoginHelper.ts @@ -2,34 +2,80 @@ import LoginPage = require("../PageObjects/HomePage/LoginPage.po") import WaitTimers = require("../Utility/WaitTimers"); -var waitUpTo = new WaitTimers(); - class Login { - public logout() { var navigationBarHelper = new LoginPage().navigationBarHelper; - navigationBarHelper.logout(); + return navigationBarHelper.logout(); } public loginAsGlobalAdmin() { - this.login(0); + return this.login(this.getCredentialsMap().globalAdmin); } public loginAsLocalAdmin() { - this.login(1); + return this.login(this.getCredentialsMap().localAdmin); } public loginAsRegularUser() { - this.login(2); + return this.login(this.getCredentialsMap().regularUser); + } + + public loginAsApiUser() { + return this.login(this.getCredentialsMap().apiUsers.regularUser); + } + + public getApiUserCredentials() { + return this.getCredentialsMap().apiUsers.regularUser; + } + + public getLocalAdminCredentials() { + return this.getCredentialsMap().localAdmin; + } + + private getCredentialsMap() { + return { + globalAdmin: this.getCredentials(0), + localAdmin: this.getCredentials(1), + regularUser: this.getCredentials(2), + apiUsers: { + regularUser: this.getCredentials(3) + } + }; } - private login(credentialsIndex: number) { + private getCredentials(credentialsIndex: number) { + return { + username: this.parseStringAsArrayAndGetIndex(browser.params.login.email, credentialsIndex), + password: this.parseStringAsArrayAndGetIndex(browser.params.login.pwd, credentialsIndex) + }; + } + + private login(credentials: any) { var homePage = new HomePage(); - homePage.getPage(); - browser.wait(homePage.isLoginAvailable(), waitUpTo.twentySeconds); - homePage.emailField.sendKeys(this.parseStringAsArrayAndGetIndex(browser.params.login.email, credentialsIndex)); - homePage.pwdField.sendKeys(this.parseStringAsArrayAndGetIndex(browser.params.login.pwd, credentialsIndex)); - homePage.loginButton.click(); + var navigationBar = new LoginPage().navigationBar; + var waitUpTo = new WaitTimers(); + var ec = protractor.ExpectedConditions; + + return homePage.getPage() + .then(() => { + return browser.wait(homePage.isLoginAvailable(), waitUpTo.twentySeconds); + }) + .then(() => { + return homePage.emailField.sendKeys(credentials.username); + }) + .then(() => { + return homePage.pwdField.sendKeys(credentials.password); + }) + .then(() => { + return homePage.loginButton.click(); + }) + .then(() => { + return browser.waitForAngular(); + }) + .then(() => { + //Await login completed before completing command + return browser.wait(ec.visibilityOf(navigationBar.dropDownMenu.dropDownElement), waitUpTo.twentySeconds); + }); } private parseStringAsArrayAndGetIndex(input: string, index: number) { diff --git a/Presentation.Web/Tests/Helpers/NavigationBarHelper.ts b/Presentation.Web/Tests/Helpers/NavigationBarHelper.ts index 94d1c5c7ea..1aa45a222c 100644 --- a/Presentation.Web/Tests/Helpers/NavigationBarHelper.ts +++ b/Presentation.Web/Tests/Helpers/NavigationBarHelper.ts @@ -3,12 +3,12 @@ var navigationBar = new NavigationBarWrapper(); class NavigationBarHelper { - public dropDownExpand(): void { - navigationBar.dropDownMenu.dropDownElement.click(); + public dropDownExpand() { + return navigationBar.dropDownMenu.dropDownElement.click(); } - public logout(): void { - navigationBar.dropDownMenu.logOut.click(); + public logout() { + return navigationBar.dropDownMenu.logOut.click(); } public isMyProfileDisplayed(): webdriver.promise.Promise{ diff --git a/Presentation.Web/Tests/Helpers/ReferenceHelper.ts b/Presentation.Web/Tests/Helpers/ReferenceHelper.ts index 642c797571..79db14b88c 100644 --- a/Presentation.Web/Tests/Helpers/ReferenceHelper.ts +++ b/Presentation.Web/Tests/Helpers/ReferenceHelper.ts @@ -9,14 +9,14 @@ var inputFields = homePage.kendoToolbarWrapper.inputFields(); class ReferenceHelper { public createReference(title: string, url: string, id: string) { - homePage.getPage(); - browser.wait(homePage.isCreateReferenceLoaded(), waitUpTo.twentySeconds); - headerButtons.createReference.click(); - browser.wait(homePage.isReferenceCreateFormLoaded(), waitUpTo.twentySeconds); - inputFields.referenceDocId.sendKeys(id); - inputFields.referenceDocTitle.sendKeys(title); - inputFields.referenceDocUrl.sendKeys(url); - headerButtons.editSaveReference.click(); + return homePage.getPage() + .then(() => browser.wait(homePage.isCreateReferenceLoaded(), waitUpTo.twentySeconds)) + .then(() => headerButtons.createReference.click()) + .then(() => browser.wait(homePage.isReferenceCreateFormLoaded(), waitUpTo.twentySeconds)) + .then(() => inputFields.referenceDocId.sendKeys(id)) + .then(() => inputFields.referenceDocTitle.sendKeys(title)) + .then(() => inputFields.referenceDocUrl.sendKeys(url)) + .then(() => headerButtons.editSaveReference.click()); } public deleteReference(id: string) { @@ -47,7 +47,4 @@ class ReferenceHelper { } -export = ReferenceHelper; - - -//referenceDocTitle referenceDocId referenceDocUrl \ No newline at end of file +export = ReferenceHelper; \ No newline at end of file diff --git a/Presentation.Web/Tests/HomePage/login.e2e.spec.ts b/Presentation.Web/Tests/HomePage/login.e2e.spec.ts index be4e838e8b..1abda04996 100644 --- a/Presentation.Web/Tests/HomePage/login.e2e.spec.ts +++ b/Presentation.Web/Tests/HomePage/login.e2e.spec.ts @@ -1,17 +1,12 @@ import Login = require("../Helpers/LoginHelper"); import LoginPage = require("../PageObjects/HomePage/LoginPage.po"); import TestFixtureWrapper = require("../Utility/TestFixtureWrapper"); -import WaitTimers = require("../Utility/waitTimers"); - -var pageObject = new LoginPage(); -var navigationBarHelper = pageObject.navigationBarHelper; -var navigationBar = pageObject.navigationBar; -var testFixture = new TestFixtureWrapper(); -var loginHelper = new Login(); -var waitUpTo = new WaitTimers(); -var ec = protractor.ExpectedConditions; describe("Being logged out, it is possible to login ", () => { + var pageObject = new LoginPage(); + var navigationBarHelper = pageObject.navigationBarHelper; + var testFixture = new TestFixtureWrapper(); + var loginHelper = new Login(); beforeEach(() => { testFixture.disableAutoBrowserWaits(); @@ -24,7 +19,6 @@ describe("Being logged out, it is possible to login ", () => { it("As global admin", () => { loginHelper.loginAsGlobalAdmin(); - browser.wait(ec.visibilityOf(navigationBar.dropDownMenu.dropDownElement), waitUpTo.twentySeconds); navigationBarHelper.dropDownExpand(); expect(navigationBarHelper.isMyProfileDisplayed()).toBeTruthy(); expect(navigationBarHelper.isGlobalAdminDisplayed()).toBeTruthy(); @@ -33,7 +27,6 @@ describe("Being logged out, it is possible to login ", () => { it("As local admin", () => { loginHelper.loginAsLocalAdmin(); - browser.wait(ec.visibilityOf(navigationBar.dropDownMenu.dropDownElement), waitUpTo.twentySeconds); navigationBarHelper.dropDownExpand(); expect(navigationBarHelper.isMyProfileDisplayed()).toBeTruthy(); expect(navigationBarHelper.isGlobalAdminDisplayed()).toBeFalsy(); @@ -42,7 +35,6 @@ describe("Being logged out, it is possible to login ", () => { it("As regular user", () => { loginHelper.loginAsRegularUser(); - browser.wait(ec.visibilityOf(navigationBar.dropDownMenu.dropDownElement), waitUpTo.twentySeconds); navigationBarHelper.dropDownExpand(); expect(navigationBarHelper.isMyProfileDisplayed()).toBeTruthy(); expect(navigationBarHelper.isGlobalAdminDisplayed()).toBeFalsy(); diff --git a/Presentation.Web/Tests/Organization/CreateUser.e2e.spec.ts b/Presentation.Web/Tests/Organization/CreateUser.e2e.spec.ts new file mode 100644 index 0000000000..3238e6851a --- /dev/null +++ b/Presentation.Web/Tests/Organization/CreateUser.e2e.spec.ts @@ -0,0 +1,71 @@ +import HomePage = require("../PageObjects/Organization/UsersPage.po"); +import TestFixtureWrapper = require("../Utility/TestFixtureWrapper"); +import Login = require("../Helpers/LoginHelper"); +import WaitTimers = require("../Utility/waitTimers"); +import createUserHelper = require("../Helpers/CreateUserHelper"); + +var testFixture = new TestFixtureWrapper(); +var userHelper = new createUserHelper(); +var pageObject = new HomePage(); +var loginHelper = new Login(); +var waitUpTo = new WaitTimers(); +var ec = protractor.ExpectedConditions; + +describe("Only Global Admins can create user with API access", + () => { + + afterEach(() => { + testFixture.cleanupState(); + }); + + it("Global Admin can enable api access on new user", () => { + loginHelper.loginAsGlobalAdmin(); + pageObject.getPage(); + browser.wait(ec.presenceOf(pageObject.createUserButton), waitUpTo.twentySeconds); + pageObject.createUserButton.click(); + expect(pageObject.hasAPiCheckBox.isDisplayed()).toBeTrue(); + }); + + it("Local Admin cannot enable api access on new user", () => { + loginHelper.loginAsLocalAdmin(); + pageObject.getPage(); + browser.wait(ec.presenceOf(pageObject.createUserButton), waitUpTo.twentySeconds); + pageObject.createUserButton.click(); + expect(pageObject.hasAPiCheckBox.isDisplayed()).toBeFalse(); + }); + + function canSetApiAccessTo(value: boolean) { + const credentials = loginHelper.getLocalAdminCredentials(); //Modify local admin instance + + return loginHelper.loginAsGlobalAdmin() + .then(() => { + return pageObject.getPage(); + }) + .then(() => { + return browser.wait(ec.presenceOf(pageObject.createUserButton), waitUpTo.twentySeconds); + }) + .then(() => { + console.log("Updating API status to " + value); + return userHelper.updateApiOnUser(credentials.username, value); + }).then(() => { + return browser.wait(ec.presenceOf(pageObject.kendoToolbarWrapper.columnHeaders().userApi), waitUpTo.twentySeconds); + }) + .then(() => { + return expect(pageObject.kendoToolbarWrapper.columnHeaders().userApi.isDisplayed()).toBeTruthy(); + }) + .then(() => { + console.log("Checking that status is updated"); + return userHelper.checkApiRoleStatusOnUser(credentials.username, value); + }); + } + + it("Global admin is able to set api access to TRUE on existing user", () => { + canSetApiAccessTo(true); + }); + + it("Global admin is able to set api access to FALSE on existing user", () => { + canSetApiAccessTo(false); + }); + }); + + diff --git a/Presentation.Web/Tests/Organization/UserOverview.e2e.spec.ts b/Presentation.Web/Tests/Organization/UserOverview.e2e.spec.ts new file mode 100644 index 0000000000..f89e7bd9cb --- /dev/null +++ b/Presentation.Web/Tests/Organization/UserOverview.e2e.spec.ts @@ -0,0 +1,55 @@ +import HomePage = require("../PageObjects/Organization/UsersPage.po"); +import TestFixtureWrapper = require("../Utility/TestFixtureWrapper"); +import Login = require("../Helpers/LoginHelper"); +import WaitTimers = require("../Utility/waitTimers"); + +var testFixture = new TestFixtureWrapper(); +var pageObject = new HomePage(); +var loginHelper = new Login(); +var waitUpTo = new WaitTimers(); +var ec = protractor.ExpectedConditions; + + +describe("Only Global and Local Admins can view API column in user overview", () => { + + beforeEach(() => { + + }); + + afterEach(() => { + testFixture.cleanupState(); + }); + + it("Global Admin can see API access attribute in overview", () => + { + loginHelper.loginAsGlobalAdmin(); + checkApiColumn(true); + }); + + it("Local Admin can see API access attribute in overview", () => { + loginHelper.loginAsLocalAdmin(); + checkApiColumn(true); + }); + + it("Regular user cannot see API access attribute in overview", () => + { + loginHelper.loginAsRegularUser(); + checkApiColumn(false); + }); + + function checkApiColumn(isColumnVisible : boolean) + { + pageObject.getPage(); + browser.wait(ec.presenceOf(pageObject.kendoToolbarWrapper.columnHeaders().userApi), waitUpTo.twentySeconds); + + if (isColumnVisible) + { + expect(pageObject.kendoToolbarWrapper.columnHeaders().userApi.isDisplayed()).toBeTruthy(); + } + else + { + expect(pageObject.kendoToolbarWrapper.columnHeaders().userApi.isDisplayed()).toBeFalsy(); + } + } + +}); \ No newline at end of file diff --git a/Presentation.Web/Tests/PageObjects/Organization/CreateUserPage.po.ts b/Presentation.Web/Tests/PageObjects/Organization/CreateUserPage.po.ts new file mode 100644 index 0000000000..8100960160 --- /dev/null +++ b/Presentation.Web/Tests/PageObjects/Organization/CreateUserPage.po.ts @@ -0,0 +1,27 @@ +import IPageObject = require("../IPageObject.po"); +import KendoToolbarHelper = require("../../Helpers/KendoToolbarHelper"); +import KendoToolbarWrapper = require("../../object-wrappers/KendoToolbarWrapper") + +class UsersPage implements IPageObject { + + public getPage(): webdriver.promise.Promise { + return browser.get(browser.baseUrl + "/#/organization/user"); + } + + public kendoToolbarHelper = new KendoToolbarHelper(); + public kendoToolbarWrapper = new KendoToolbarWrapper(); + + public inputEmail = element(by.model("ctrl.vm.email")); + public inputEmailRepeat = element(by.model("ctrl.repeatEmail")); + public inputName = element(by.model("ctrl.vm.name")); + public inputLastName = element(by.model("ctrl.vm.lastName")); + public inputPhone = element(by.model("ctrl.vm.phoneNumber")); + public boolApi = element(by.model("ctrl.vm.hasApi")); + public boolLocalAdmin = element(by.model("ctrl.vm.isLocalAdmin")); + public boolReadOnly = element(by.model("ctrl.vm.isReadOnly")); + public createUserButton = element(by.buttonText("Opret bruger")); + public editUserButton = element(by.buttonText("Gem ændringer")); + public cancelEditUserButton = element(by.buttonText("Annuller")); +} + +export = UsersPage; \ No newline at end of file diff --git a/Presentation.Web/Tests/PageObjects/Organization/UsersPage.po.ts b/Presentation.Web/Tests/PageObjects/Organization/UsersPage.po.ts new file mode 100644 index 0000000000..54be53ca3a --- /dev/null +++ b/Presentation.Web/Tests/PageObjects/Organization/UsersPage.po.ts @@ -0,0 +1,20 @@ +import IPageObject = require("../IPageObject.po"); +import KendoToolbarHelper = require("../../Helpers/KendoToolbarHelper"); +import KendoToolbarWrapper = require("../../object-wrappers/KendoToolbarWrapper") + +class UsersPage implements IPageObject { + + public getPage(): webdriver.promise.Promise { + return browser.get(browser.baseUrl + "/#/organization/user"); + } + + public kendoToolbarHelper = new KendoToolbarHelper(); + public kendoToolbarWrapper = new KendoToolbarWrapper(); + public createUserButton = element(by.linkText("Opret Bruger")); + public hasAPiCheckBox = element(by.model("ctrl.vm.hasApi")); + + public mainGridAllTableRows = element.all(by.id("mainGrid")).all(by.tagName("tr")); + +} + +export = UsersPage; \ No newline at end of file diff --git a/Presentation.Web/Tests/PageObjects/it-system/Catalog/ItSystemCatalog.po.ts b/Presentation.Web/Tests/PageObjects/it-system/Catalog/ItSystemCatalog.po.ts index a17272ce68..e54bf65375 100644 --- a/Presentation.Web/Tests/PageObjects/it-system/Catalog/ItSystemCatalog.po.ts +++ b/Presentation.Web/Tests/PageObjects/it-system/Catalog/ItSystemCatalog.po.ts @@ -11,7 +11,16 @@ var byDataElementType = new CssLocatorHelper().byDataElementType; class ItSystemCatalog implements IPageObject { public getPage(): webdriver.promise.Promise { - return browser.get(browser.baseUrl + "/#/system/catalog"); + return browser.getCurrentUrl() + .then(url => { + const navigateToUrl = browser.baseUrl + "/#/system/catalog"; + if (navigateToUrl !== url) { + console.log("Not at " + navigateToUrl + " but at:" + url + ". Navigating to:" + navigateToUrl); + return browser.get(browser.baseUrl + "/#/system/catalog"); + } else { + console.log("Already at " + navigateToUrl + ". Ignoring command"); + } + }); } public kendoToolbarHelper = new KendoToolbarHelper(); diff --git a/Presentation.Web/Tests/Utility/Constants.ts b/Presentation.Web/Tests/Utility/Constants.ts index fb8a4024f9..7a6d4bee03 100644 --- a/Presentation.Web/Tests/Utility/Constants.ts +++ b/Presentation.Web/Tests/Utility/Constants.ts @@ -21,10 +21,19 @@ class Constants { readonly kendoCreateReferenceButton = "createReferenceButton"; + readonly kendoUserEmailHeader = "userHeaderEmail"; + readonly kendoUserEmailObject = "userEmailObject"; + readonly kendoUserApiHeader = "userHeader"; + readonly kendoUserApiObject = "userObject"; + readonly kendoResetFilter = "resetFilterButton"; + readonly kendoSaveFilter = "saveFilterButton"; + readonly kendoUseFilter = "useFilterButton"; + readonly kendoRemoveFilter = "removeFilterButton"; + readonly kendoUserDeleteButton = "userDeleteButton"; + //Environmental variables readonly defaultItContractName = "DefaultTestItContract"; readonly contractNameVariable = "Name"; - readonly defaultCatalog = "katalog123"; readonly nameOfSystemInput = "nameOfItSystemInput"; readonly saveCatalogButton = "itCatalogSaveButton"; readonly loginFormField = "loginFormField"; diff --git a/Presentation.Web/Tests/Utility/TestFixtureWrapper.ts b/Presentation.Web/Tests/Utility/TestFixtureWrapper.ts index 815d6ab5b5..fca3ae83d5 100644 --- a/Presentation.Web/Tests/Utility/TestFixtureWrapper.ts +++ b/Presentation.Web/Tests/Utility/TestFixtureWrapper.ts @@ -1,4 +1,6 @@ -class TestFixtureWrapper { +const defaultJasmineTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; + +class TestFixtureWrapper { public cleanupState() { browser.driver.manage().deleteAllCookies(); } @@ -11,8 +13,14 @@ browser.ignoreSynchronization = false; } + public enableLongRunningTest() { + const minutes = 2; + jasmine.DEFAULT_TIMEOUT_INTERVAL = minutes * 60 * 1000; + } - + public disableLongRunningTest() { + jasmine.DEFAULT_TIMEOUT_INTERVAL = defaultJasmineTimeout; + } } export = TestFixtureWrapper; \ No newline at end of file diff --git a/Presentation.Web/Tests/it-system/Catalog/LocalAdmin.Catalog.e2e.spec.ts b/Presentation.Web/Tests/it-system/Catalog/LocalAdmin.Catalog.e2e.spec.ts deleted file mode 100644 index 221913b15d..0000000000 --- a/Presentation.Web/Tests/it-system/Catalog/LocalAdmin.Catalog.e2e.spec.ts +++ /dev/null @@ -1,47 +0,0 @@ -import Login = require("../../Helpers/LoginHelper"); -import CatalogHelper = require("../../Helpers/CatalogHelper"); -import ItSystemEditPo = require("../../PageObjects/it-system/Catalog/ItSystemCatalog.po") -import Constants = require("../../Utility/Constants"); -import WaitTimers = require("../../Utility/WaitTimers"); -import TestFixtureWrapper = require("../../Utility/TestFixtureWrapper"); - -describe("LocalAdmin user tests", () => { - var loginHelper = new Login(); - var pageObject = new ItSystemEditPo(); - var consts = new Constants(); - var waitUpTo = new WaitTimers(); - var testFixture = new TestFixtureWrapper(); - - beforeAll(() => { - loginHelper.loginAsLocalAdmin(); - browser.waitForAngular(); - }); - - afterAll(() => { - testFixture.cleanupState(); - }); - - it("Can create catalog and delete it again", () => { - pageObject.getPage(); - browser.wait(pageObject.waitForKendoGrid(), waitUpTo.twentySeconds); - expect(pageObject.kendoToolbarWrapper.getFilteredColumnElement(pageObject.kendoToolbarWrapper.columnObjects() - .catalogName, - consts.defaultCatalog)).toBeEmptyArray(); - CatalogHelper.createCatalog(consts.defaultCatalog); - pageObject.getPage(); - expect(pageObject.kendoToolbarWrapper.getFilteredColumnElement(pageObject.kendoToolbarWrapper.columnObjects() - .catalogName, - consts.defaultCatalog).first().getText()).toEqual(consts.defaultCatalog); - CatalogHelper.deleteCatalog(consts.defaultCatalog); - pageObject.getPage(); - browser.wait(pageObject.waitForKendoGrid(), waitUpTo.twentySeconds); - expect(pageObject.kendoToolbarWrapper.getFilteredColumnElement(pageObject.kendoToolbarWrapper.columnObjects() - .catalogName, - consts.defaultCatalog)).toBeEmptyArray(); - }); - -}); - - - - diff --git a/Presentation.Web/Tests/it-system/Catalog/User.Accessibility.Catalog.e2e.spec.ts b/Presentation.Web/Tests/it-system/Catalog/User.Accessibility.Catalog.e2e.spec.ts new file mode 100644 index 0000000000..5fa05011f5 --- /dev/null +++ b/Presentation.Web/Tests/it-system/Catalog/User.Accessibility.Catalog.e2e.spec.ts @@ -0,0 +1,161 @@ +import Login = require("../../Helpers/LoginHelper"); +import CatalogHelper = require("../../Helpers/CatalogHelper"); +import ItSystemEditPo = require("../../PageObjects/it-system/Catalog/ItSystemCatalog.po") +import TestFixtureWrapper = require("../../Utility/TestFixtureWrapper"); + +describe("ITSystem Catalog accessibility tests", () => { + var loginHelper = new Login(); + var pageObject = new ItSystemEditPo(); + var testFixture = new TestFixtureWrapper(); + var findCatalogColumnsFor = CatalogHelper.findCatalogColumnsFor; + + afterEach(() => { + testFixture.cleanupState(); + }); + + beforeAll(() => { + testFixture.enableLongRunningTest(); + }); + + afterAll(() => { + testFixture.disableLongRunningTest(); + }); + + it("Local Admin cannot create items in It-system catalog", () => { + loginHelper.loginAsLocalAdmin() + .then(() => { + return loadPage(); + }) + .then(() => { + return waitForKendoGrid(); + }) + .then(() => { + return expectCreateButtonVisibility(false); + }); + }); + + it("Regular user cannot create items in IT-system catalog", () => { + loginHelper.loginAsRegularUser() + .then(() => { + return loadPage(); + }) + .then(() => { + return waitForKendoGrid(); + }) + .then(() => { + return expectCreateButtonVisibility(false); + }); + }); + + it("Local Admin can still delete IT-system Catalogs that have been created locally", () => { + const catalogName = createCatalogName(); + loginHelper.loginAsGlobalAdmin() + .then(() => { + return loadPage(); + }).then(() => { + return waitForKendoGrid(); + }).then(() => { + return expectCreateButtonVisibility(true); + }).then(() => { + return expectNoCatalogWithName(catalogName); + }).then(() => { + console.log("Creating catalog"); + return CatalogHelper.createCatalog(catalogName); + }).then(() => { + console.log("Deleting cookies"); + return testFixture.cleanupState(); + }).then(() => { + console.log("Logging in as Local Admin"); + return loginHelper.loginAsLocalAdmin(); + }).then(() => { + return loadPage(); + }).then(() => { + return waitForKendoGrid(); + }).then(() => { + return expectCatalogWithName(catalogName); + }).then(() => { + console.log("Deleting catalog"); + return CatalogHelper.deleteCatalog(catalogName); + }).then(() => { + return loadPage(); + }).then(() => { + return waitForKendoGrid(); + }).then(() => { + console.log("Checking that the catalog have been deleted"); + return expectNoCatalogWithName(catalogName); + }); + }); + + it("Global Admin can create and delete It-system catalog", () => { + const catalogName = createCatalogName(); + loginHelper.loginAsGlobalAdmin() + .then(() => { + return loadPage(); + }) + .then(() => { + return waitForKendoGrid(); + }) + .then(() => { + return expectCreateButtonVisibility(true); + }) + .then(() => { + return expectNoCatalogWithName(catalogName); + }) + .then(() => { + console.log("Creating catalog"); + return CatalogHelper.createCatalog(catalogName); + }) + .then(() => { + console.log("Loading page after catalog creation"); + return loadPage(); + }) + .then(() => { + return waitForKendoGrid(); + }) + .then(() => { + return expectCatalogWithName(catalogName); + }) + .then(() => { + console.log("Deleting catalog"); + return CatalogHelper.deleteCatalog(catalogName); + }) + .then(() => { + console.log("Verify that catalog is deleted"); + return loadPage(); + }) + .then(() => { + return waitForKendoGrid(); + }) + .then(() => { + return expectNoCatalogWithName(catalogName); + }); + }); + + function expectCreateButtonVisibility(expectedEnabledState: boolean){ + console.log("Expecting createCatalog visibility to be:" + expectedEnabledState); + return expect(pageObject.kendoToolbarWrapper.headerButtons().systemCatalogCreate.isEnabled()).toBe(expectedEnabledState); + } + + function waitForKendoGrid() { + return CatalogHelper.waitForKendoGrid(); + } + + function loadPage() { + console.log("Loading catalog page"); + return pageObject.getPage(); + } + + function createCatalogName() { + return "Catalog" + new Date().getTime(); + } + + function expectCatalogWithName(name: string) { + console.log("Making sure " + name + " does exist"); + return expect(findCatalogColumnsFor(name).first().getText()).toEqual(name); + } + + function expectNoCatalogWithName(name: string) { + console.log("Making sure " + name + " does not exist"); + return expect(findCatalogColumnsFor(name)).toBeEmptyArray(); + } +}); \ No newline at end of file diff --git a/Presentation.Web/Tests/it-system/tabs/LocalAdmin.Reference.System.e2e.spec.ts b/Presentation.Web/Tests/it-system/tabs/LocalAdmin.Reference.System.e2e.spec.ts index b8d215072b..e4112b0dc8 100644 --- a/Presentation.Web/Tests/it-system/tabs/LocalAdmin.Reference.System.e2e.spec.ts +++ b/Presentation.Web/Tests/it-system/tabs/LocalAdmin.Reference.System.e2e.spec.ts @@ -47,7 +47,6 @@ describe("Can edit reference URL", beforeAll(() => { loginHelper.loginAsLocalAdmin(); - browser.waitForAngular(); refHelper.createReference(consts.refTitle, consts.validUrl, consts.refId); }); diff --git a/Presentation.Web/Tests/object-wrappers/kendoToolbarWrapper.ts b/Presentation.Web/Tests/object-wrappers/kendoToolbarWrapper.ts index dbc79535e9..ea226316bf 100644 --- a/Presentation.Web/Tests/object-wrappers/kendoToolbarWrapper.ts +++ b/Presentation.Web/Tests/object-wrappers/kendoToolbarWrapper.ts @@ -29,8 +29,9 @@ type ColumnHeaders = { referenceId: protractor.ElementFinder, contractName: protractor.ElementFinder, catalogName: protractor.ElementFinder, - catalogUsage: protractor.ElementFinder - + catalogUsage: protractor.ElementFinder, + userApi: protractor.ElementFinder, + userEmail: protractor.ElementFinder }; type ColumnObjects = { @@ -39,7 +40,9 @@ type ColumnObjects = { referenceId: protractor.ElementArrayFinder, contractName: protractor.ElementArrayFinder, catalogName: protractor.ElementArrayFinder, - catalogUsage: protractor.ElementArrayFinder + catalogUsage: protractor.ElementArrayFinder, + userApi: protractor.ElementArrayFinder, + UserEmail: protractor.ElementArrayFinder }; var byDataElementType = new CSSLocator().byDataElementType; @@ -85,7 +88,9 @@ class kendoToolbarWrapper { catalogName: kendo.getColumnHeaderClickable(consts.kendoCatalogNameHeader), catalogUsage: kendo.getColumnHeaderClickable(consts.kendoCatalogUsageHeader), referenceName: kendo.getColumnHeaderClickable(consts.kendoReferencetNameHeader), - referenceId: kendo.getColumnHeaderClickable(consts.kendoReferenceHeaderId) + referenceId: kendo.getColumnHeaderClickable(consts.kendoReferenceHeaderId), + userApi: kendo.getUserColumnHeaderClickable(consts.kendoUserApiHeader), + userEmail: kendo.getColumnHeaderClickable(consts.kendoUserEmailHeader) }; return columns; @@ -100,6 +105,8 @@ class kendoToolbarWrapper { contractName: kendo.getColumnItemLinks(consts.kendoContractNameObjects), catalogName: kendo.getColumnItemLinks(consts.kendoCatalogNameObjects), catalogUsage: kendo.getColumnItemLinks(consts.kendoCatalogUsageObjects), + userApi: kendo.getColumnItemLinks(consts.kendoUserApiObject), + UserEmail: kendo.getColumnItemLinks(consts.kendoUserEmailObject), referenceName: kendo.getColumnItemLinks(consts.kendoReferenceNameObjects), referenceId: kendo.getColumnItemLinks(consts.kendoReferenceHeaderIdObjects) }; @@ -124,6 +131,10 @@ class kendoHelper { return element(byDataElementType(headerHook)).element(by.css("a[class=k-link]")); } + public getUserColumnHeaderClickable(headerHook: string) { + return element(byDataElementType(headerHook)).element(by.css("a[class=k-header-column-menu]")); + } + public getColumnItemLinks(itemHook: string) { return element.all(byDataElementType(itemHook)).all(by.tagName("a")); } diff --git a/Presentation.Web/Web.Debug.config b/Presentation.Web/Web.Debug.config deleted file mode 100644 index 3a271591e3..0000000000 --- a/Presentation.Web/Web.Debug.config +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - https://kitostest.miracle.dk/ - - - (i testmiljøet) - - - Test - - - - diff --git a/Presentation.Web/Web.Prod.config b/Presentation.Web/Web.Prod.config deleted file mode 100644 index 4c8449a934..0000000000 --- a/Presentation.Web/Web.Prod.config +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 10.00:00:00 - - - https://www.kitos.dk/ - - - - - - Prod - - - - \ No newline at end of file diff --git a/Presentation.Web/Web.Release.config b/Presentation.Web/Web.Release.config index cc06315d78..1c4bfa4cb7 100644 --- a/Presentation.Web/Web.Release.config +++ b/Presentation.Web/Web.Release.config @@ -8,19 +8,6 @@ "connectionString" to use "ReleaseSQLServer" only when the "Match" locator finds an atrribute "name" that has a value of "MyDB". --> - - - - - - - - - @@ -33,9 +20,10 @@ - - - - - - - - https://kitostest.miracle.dk/ - - - (i testmiljøet) - - - Test - - - + + diff --git a/Presentation.Web/Web.Test.config b/Presentation.Web/Web.Test.config deleted file mode 100644 index a790be9b9c..0000000000 --- a/Presentation.Web/Web.Test.config +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - https://kitostest.miracle.dk/ - - - (i testmiljøet) - - - Test - - - - diff --git a/Presentation.Web/Web.config b/Presentation.Web/Web.config index e8eb42bc4b..eed85a42d4 100644 --- a/Presentation.Web/Web.config +++ b/Presentation.Web/Web.config @@ -13,8 +13,8 @@ - - + + @@ -35,6 +35,7 @@ --> + @@ -106,7 +107,7 @@ - + @@ -134,15 +135,15 @@ - + - + - + @@ -170,12 +171,32 @@ - + + + + + + + + + + + + + + + + + + + + + @@ -200,6 +221,12 @@ unknown + + arne123 + + + true + + {{orgUnit.Object.Name}} @@ -68,7 +70,7 @@
- + {{project.Object.Name}} {{project.Role.Name}} {{ctrl.selectedUser.Name}} {{ctrl.selectedUser.LastName}} @@ -166,7 +168,7 @@
- + diff --git a/Presentation.Web/app/components/org/user/org-user-edit.controller.ts b/Presentation.Web/app/components/org/user/org-user-edit.controller.ts index 26b8b02041..d6b3e2cebf 100644 --- a/Presentation.Web/app/components/org/user/org-user-edit.controller.ts +++ b/Presentation.Web/app/components/org/user/org-user-edit.controller.ts @@ -6,6 +6,7 @@ email: string; lastName: string; phoneNumber: string; + hasApi: boolean; isLocalAdmin: boolean; isOrgAdmin: boolean; isProjectAdmin: boolean; @@ -13,6 +14,8 @@ isContractAdmin: boolean; isReportAdmin: boolean; isReadOnly: boolean; + + } class EditOrganizationUserController { @@ -25,6 +28,7 @@ public isUserContractAdmin = false; public isUserReportAdmin = false; public isUserReadOnly = false; + public hasApi = false; private userId: number; private originalVm; @@ -42,6 +46,7 @@ var userVm: IEditViewModel = { email: user.Email, name: user.Name, + hasApi: user.HasApiAccess, lastName: user.LastName, phoneNumber: user.PhoneNumber, isLocalAdmin: _.find(user.OrganizationRights, { Role: Models.OrganizationRole.LocalAdmin }) !== undefined, @@ -53,8 +58,9 @@ isReadOnly: _.find(user.OrganizationRights, { Role: Models.OrganizationRole.ReadOnly }) !== undefined }; this.originalVm = _.clone(userVm); - this.vm = userVm; + this.vm = userVm; + this.hasApi = currentUser.hasApi; this.isUserGlobalAdmin = currentUser.isGlobalAdmin; this.isUserLocalAdmin = currentUser.isLocalAdmin; this.isUserOrgAdmin = currentUser.isOrgAdmin; @@ -68,7 +74,9 @@ private changeRight(diffRights, property: string, role: Models.OrganizationRole): ng.IHttpPromise { // check if the requested property exsists in the diff if (Object.keys(diffRights).indexOf(property) === -1) + { return; // if it doesn't then it wasn't changed and we abort + } if (diffRights[property]) { // add role to user @@ -102,8 +110,11 @@ Name: this.vm.name, LastName: this.vm.lastName, PhoneNumber: this.vm.phoneNumber, - Email: this.vm.email - }; + Email: this.vm.email, + HasApiAccess: this.vm.hasApi + + + }; this.$http.patch(`/odata/Users(${this.userId})`, payload); // when all requests are done diff --git a/Presentation.Web/app/components/org/user/org-user-edit.modal.view.html b/Presentation.Web/app/components/org/user/org-user-edit.modal.view.html index cff06555e7..48fe62c845 100644 --- a/Presentation.Web/app/components/org/user/org-user-edit.modal.view.html +++ b/Presentation.Web/app/components/org/user/org-user-edit.modal.view.html @@ -65,6 +65,14 @@

Redigér bruger

+ +
+
+ +
+
diff --git a/Presentation.Web/app/components/org/user/org-user.controller.ts b/Presentation.Web/app/components/org/user/org-user.controller.ts index dbe8ac8221..64bd16e902 100644 --- a/Presentation.Web/app/components/org/user/org-user.controller.ts +++ b/Presentation.Web/app/components/org/user/org-user.controller.ts @@ -1,7 +1,8 @@ module Kitos.Organization.Users { "use strict"; - interface IGridModel extends Models.IUser { + interface IGridModel extends Models.IUser { + hasApi: boolean; canEdit: boolean; isLocalAdmin: boolean; isOrgAdmin: boolean; @@ -34,7 +35,7 @@ type: "odata-v4", transport: { read: { - url: `/odata/Organizations(${this.user.currentOrganizationId})/Users`, + url: `/odata/Organizations(${this.user.currentOrganizationId})/Organizations.GetUsers`, dataType: "json", data: { $expand: `ObjectOwner,OrganizationUnitRights($expand=Role($select=Name)),OrganizationRights($filter=OrganizationId eq ${this.user.currentOrganizationId})` @@ -105,6 +106,7 @@ usr.isContractAdmin = this._.find(usr.OrganizationRights, (right) => right.Role === Models.OrganizationRole.ContractModuleAdmin) !== undefined; usr.isReportAdmin = this._.find(usr.OrganizationRights, (right) => right.Role === Models.OrganizationRole.ReportModuleAdmin) !== undefined; usr.isReadOnly = this._.find(usr.OrganizationRights, (right) => right.Role === Models.OrganizationRole.ReadOnly) !== undefined; + }); return response; } @@ -123,7 +125,7 @@ sortable: { mode: "single" }, - editable: true, + editable: false, reorderable: true, resizable: true, filterable: { @@ -160,6 +162,12 @@ persistId: "email", // DON'T YOU DARE RENAME! template: (dataItem) => `${dataItem.Email}`, excelTemplate: (dataItem) => dataItem.Email, + headerAttributes: { + "data-element-type": "userHeaderEmail" + }, + attributes: { + "data-element-type": "userEmailObject" + }, hidden: false, filterable: { cell: { @@ -211,6 +219,20 @@ } } }, + { + + field: "hasApi", title: "API adgang", width: 96, + persistId: "apiaccess", // DON'T YOU DARE RENAME! + attributes: { "class": "text-center", "data-element-type": "userObject"}, + headerAttributes: { + "data-element-type": "userHeader" + }, + template: (dataItem) => dataItem.HasApiAccess ? `` : ``, + hidden: !(this.user.isGlobalAdmin || this.user.isLocalAdmin), + filterable: false, + sortable: false, + menu: (this.user.isGlobalAdmin || this.user.isLocalAdmin), + }, { field: "isLocalAdmin", title: "Lokal Admin", width: 96, persistId: "localadminrole", // DON'T YOU DARE RENAME! diff --git a/Presentation.Web/app/models/organization/organization-role.ts b/Presentation.Web/app/models/organization/organization-role.ts index 816fec3d52..b32ba546e3 100644 --- a/Presentation.Web/app/models/organization/organization-role.ts +++ b/Presentation.Web/app/models/organization/organization-role.ts @@ -18,6 +18,7 @@ /** Has write access to everything within the report module */ ReportModuleAdmin = "ReportModuleAdmin" as any, /** Has readonly access */ - ReadOnly = "ReadOnly" as any + ReadOnly = "ReadOnly" as any, + } } diff --git a/Presentation.Web/app/models/user.ts b/Presentation.Web/app/models/user.ts index 827463ed17..add3ee5180 100644 --- a/Presentation.Web/app/models/user.ts +++ b/Presentation.Web/app/models/user.ts @@ -5,6 +5,7 @@ PhoneNumber?: string; Email?: string; IsGlobalAdmin?: boolean; + HasApiAccess?: boolean; Uuid?: any; LastAdvisDate?: Date; /** The admin rights of the user */ @@ -40,6 +41,7 @@ Email: string; PhoneNumber?: string; IsGlobalAdmin?: boolean; + HasApiAccess?: boolean; } export interface ICreateUserPayload { diff --git a/Presentation.Web/app/services/userServices.ts b/Presentation.Web/app/services/userServices.ts index 17b3b1317f..c3f8bf5bbc 100644 --- a/Presentation.Web/app/services/userServices.ts +++ b/Presentation.Web/app/services/userServices.ts @@ -17,6 +17,7 @@ isContractAdmin: boolean; isReportAdmin: boolean; isReadOnly: boolean; + hasApi: boolean; orgAndDefaultUnit: any; @@ -82,6 +83,7 @@ var isReadOnly = this._.some(user.organizationRights, function (userRight: { role; organizationId; }) { return userRight.role == Kitos.API.Models.OrganizationRole.ReadOnly && userRight.organizationId == currOrg.id; }); + // the current org unit is the default org unit for this organization if the user has selected one // otherwise it's the root of this organization @@ -110,6 +112,7 @@ email: user.email, phoneNumber: user.phoneNumber, uuid: user.uuid, + hasApi: user.hasApi, defaultUserStartPreference: user.defaultUserStartPreference || "index", isGlobalAdmin: user.isGlobalAdmin, @@ -120,6 +123,7 @@ isContractAdmin: isContractAdmin, isReportAdmin: isReportAdmin, isReadOnly: isReadOnly, + orgAndDefaultUnit: orgAndDefaultUnit, diff --git a/Presentation.Web/app/shared/selectUser/selectUser.directive.ts b/Presentation.Web/app/shared/selectUser/selectUser.directive.ts index b5d6f3ebc4..f915f76745 100644 --- a/Presentation.Web/app/shared/selectUser/selectUser.directive.ts +++ b/Presentation.Web/app/shared/selectUser/selectUser.directive.ts @@ -123,7 +123,7 @@ { ignoreLoadingBar: true }); } else { result = $http - .get(`/odata/Organizations(${$scope.orgId})/Users` + urlAddition, + .get(`/odata/Organizations(${$scope.orgId})/Organizations.GetUsers` + urlAddition, //.get(`/odata/Organizations(${$scope.orgId})/Users?$filter=contains(concat(concat(concat(concat(tolower(Name), ' '), tolower(LastName)), ' '), tolower(Email)), tolower('${userInputString[0]}')) and contains(concat(concat(concat(concat(tolower(Name), ' '), tolower(LastName)), ' '), tolower(Email)), tolower('${userInputString[1]}')) and contains(concat(concat(concat(concat(tolower(Name), ' '), tolower(LastName)), ' '), tolower(Email)), tolower('${userInputString[2]}'))`, { ignoreLoadingBar: true }); diff --git a/Presentation.Web/app/utility/validation.ts b/Presentation.Web/app/utility/validation.ts index 1cc7828bb7..976413171e 100644 --- a/Presentation.Web/app/utility/validation.ts +++ b/Presentation.Web/app/utility/validation.ts @@ -1,10 +1,8 @@ -module Kitos.Utility -{ - export class Validation - { - static validateUrl(url : string) : boolean { +module Kitos.Utility { + export class Validation { + static validateUrl(url: string): boolean { - const regexp = /(http || https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/; + const regexp = /(^https?):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/])$)?/; return regexp.test(url.toLowerCase()); } diff --git a/Presentation.Web/packages.config b/Presentation.Web/packages.config index f93df74e8e..e08642ab70 100644 --- a/Presentation.Web/packages.config +++ b/Presentation.Web/packages.config @@ -13,7 +13,7 @@ - + @@ -22,17 +22,26 @@ - - - - - + + + + + + + + + + + - + + + + - + @@ -46,8 +55,22 @@ - - + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests.Integration.Presentation.Web/ItSystem/ItInterfaceUsageTests.cs b/Tests.Integration.Presentation.Web/ItSystem/ItInterfaceUsageTests.cs new file mode 100644 index 0000000000..282695a167 --- /dev/null +++ b/Tests.Integration.Presentation.Web/ItSystem/ItInterfaceUsageTests.cs @@ -0,0 +1,51 @@ +using System; +using System.Net; +using System.Threading.Tasks; +using Core.DomainModel; +using Core.DomainModel.Organization; +using Presentation.Web.Models; +using Tests.Integration.Presentation.Web.Tools; +using Xunit; + +namespace Tests.Integration.Presentation.Web.ItSystem +{ + public class ItInterfaceUsageTests : WithAutoFixture + { + + [Theory] + [InlineData(OrganizationRole.GlobalAdmin)] + public async Task Api_User_Can_Get_It_Interface_Usage(OrganizationRole role) + { + //Arrange + var dto = CreateNewInterfaceDto(); + + var createdInterface = await InterfaceHelper.CreateInterface(dto); + await InterfaceHelper.CreateItInterfaceUsageAsync( + TestEnvironment.DefaultItSystemUsageId, + createdInterface.Id, + TestEnvironment.DefaultItSystemId, + dto.OrganizationId, + TestEnvironment.DefaultContractId); + + var token = await HttpApi.GetTokenAsync(role); + var url = TestEnvironment.CreateUrl($"api/ItInterfaceUsage?usageId={TestEnvironment.DefaultItSystemUsageId}&sysId={TestEnvironment.DefaultItSystemId}&interfaceId={createdInterface.Id}"); + + //Act + using (var httpResponse = await HttpApi.GetWithTokenAsync(url, token.Token)) + { + var response = await httpResponse.ReadResponseBodyAsKitosApiResponseAsync(); + //Assert + Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode); + Assert.Equal(TestEnvironment.DefaultItSystemUsageId, response.ItSystemUsageId); + Assert.Equal(TestEnvironment.DefaultItSystemId, response.ItSystemId); + Assert.Equal(createdInterface.Id, response.ItInterfaceId); + Assert.Equal(TestEnvironment.DefaultContractId, response.ItContractId); + } + } + + private ItInterfaceDTO CreateNewInterfaceDto() + { + return InterfaceHelper.CreateInterfaceDto(A().ToString("N"), A().ToString("N"), TestEnvironment.DefaultUserId, TestEnvironment.DefaultOrganizationId, AccessModifier.Local); + } + } +} diff --git a/Tests.Integration.Presentation.Web/ItSystem/ItIterfacesTest.cs b/Tests.Integration.Presentation.Web/ItSystem/ItIterfacesTest.cs new file mode 100644 index 0000000000..e799a372c3 --- /dev/null +++ b/Tests.Integration.Presentation.Web/ItSystem/ItIterfacesTest.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Core.DomainModel; +using Core.DomainModel.ItSystem; +using Core.DomainModel.Organization; +using Presentation.Web.Models; +using Tests.Integration.Presentation.Web.Tools; +using Xunit; + +namespace Tests.Integration.Presentation.Web.ItSystem +{ + public class ItIterfacesTest : WithAutoFixture + { + private int _defaultUserId; + + [Fact] + public async Task Global_Administrator_Can_Get_All_Interfaces() + { + //Arrange + var interFacePrefixName = CreateInterFacePrefixName(); + var interfacesCreated = await GenerateTestInterfaces(interFacePrefixName); + var url = TestEnvironment.CreateUrl($"odata/ItInterfaces"); + var token = await HttpApi.GetTokenAsync(OrganizationRole.GlobalAdmin); + + //Act + using (var httpResponse = await HttpApi.GetWithTokenAsync(url, token.Token)) + { + //Assert + var response = await httpResponse.ReadOdataListResponseBodyAsAsync(); + Assert.NotNull(response); + var filteredResult = response.Where(x => x.Name.StartsWith(interFacePrefixName)).ToList(); + Assert.Equal(interfacesCreated.Length, filteredResult.Count); + Assert.True(interfacesCreated.Select(x => x.InterfaceId).SequenceEqual(filteredResult.Select(x => x.InterfaceId))); + } + } + + [Theory] + [InlineData(OrganizationRole.GlobalAdmin, TestEnvironment.DefaultOrganizationId)] + [InlineData(OrganizationRole.GlobalAdmin, TestEnvironment.SecondOrganizationId)] + [InlineData(OrganizationRole.User, TestEnvironment.DefaultOrganizationId)] + [InlineData(OrganizationRole.User, TestEnvironment.SecondOrganizationId)] + public async Task User_Is_Able_To_Get_Interfaces_From_Own_Org_Or_Public(OrganizationRole role, int orgId) + { + //Arrabge + var interFacePrefixName = CreateInterFacePrefixName(); + var token = await HttpApi.GetTokenAsync(role); + var interfacesCreated = await GenerateTestInterfaces(interFacePrefixName); + var expectedResults = interfacesCreated.Where(x => x.OrganizationId == orgId || x.AccessModifier == AccessModifier.Public).ToList(); + var url = TestEnvironment.CreateUrl($"/odata/Organizations({orgId})/ItInterfaces"); + + //Act + using (var httpResponse = await HttpApi.GetWithTokenAsync(url, token.Token)) + { + //Assert + var response = await httpResponse.ReadOdataListResponseBodyAsAsync(); + Assert.NotNull(response); + var filteredResult = response.Where(x => x.Name.StartsWith(interFacePrefixName)).ToList(); + Assert.Equal(expectedResults.Count, filteredResult.Count); + Assert.True(expectedResults.Select(x => x.InterfaceId).SequenceEqual(filteredResult.Select(x => x.InterfaceId))); + } + } + + [Theory] + [InlineData(OrganizationRole.GlobalAdmin)] + public async Task User_Is_Able_To_See_Specific_Interface_From_Own_Org_Or_public(OrganizationRole role) + { + //Arrange + var interFacePrefixName = CreateInterFacePrefixName(); + var token = await HttpApi.GetTokenAsync(role); + await GenerateTestInterfaces(interFacePrefixName); + var interfaceResultByName = await GetInterfacesByName(interFacePrefixName); + + foreach (var item in interfaceResultByName.Result) + { + var orgFromItem = item.OrganizationId; + var key = item.Id; + + var url = TestEnvironment.CreateUrl($"odata/Organizations({orgFromItem})/ItInterfaces({key})"); + + //Act + using (var httpResponse = await HttpApi.GetWithTokenAsync(url, token.Token)) + { + //Assert + var response = await httpResponse.ReadResponseBodyAsAsync(); + Assert.NotNull(interfaceResultByName); + Assert.Equal(key, response.Id); + } + } + } + + private string CreateInterFacePrefixName() + { + return $"{nameof(ItIterfacesTest)}-{A():N}"; + } + + private static async Task>> GetInterfacesByName(string name) + { + var token = await HttpApi.GetTokenAsync(OrganizationRole.GlobalAdmin); + var arrangeUrl = TestEnvironment.CreateUrl($"/odata/ItInterfaces?$filter=contains(Name,'{name}')"); + using (var httpResponse = await HttpApi.GetWithTokenAsync(arrangeUrl, token.Token)) + { + var response = httpResponse.ReadOdataListResponseBodyAsAsync(); + return response; + } + } + + private async Task GenerateTestInterfaces(string name) + { + _defaultUserId = TestEnvironment.DefaultUserId; + var itInterfaceDto1 = InterfaceHelper.CreateInterfaceDto($"{name}-{A():N}", A().ToString(), _defaultUserId, TestEnvironment.DefaultOrganizationId, AccessModifier.Local); + var itInterfaceDto2 = InterfaceHelper.CreateInterfaceDto($"{name}-{A():N}", A().ToString(), _defaultUserId, TestEnvironment.DefaultOrganizationId, AccessModifier.Public); + var itInterfaceDto3 = InterfaceHelper.CreateInterfaceDto($"{name}-{A():N}", A().ToString(), _defaultUserId, TestEnvironment.SecondOrganizationId, AccessModifier.Local); + var itInterfaceDto4 = InterfaceHelper.CreateInterfaceDto($"{name}-{A():N}", A().ToString(), _defaultUserId, TestEnvironment.SecondOrganizationId, AccessModifier.Public); + await InterfaceHelper.CreateInterfaces(itInterfaceDto1, itInterfaceDto2, itInterfaceDto3, itInterfaceDto4); + return new[] + { + itInterfaceDto1, + itInterfaceDto2, + itInterfaceDto3, + itInterfaceDto4 + }; + } + } +} diff --git a/Tests.Integration.Presentation.Web/ItSystem/ItSystemCatalogTests.cs b/Tests.Integration.Presentation.Web/ItSystem/ItSystemCatalogTests.cs new file mode 100644 index 0000000000..84db10c9b4 --- /dev/null +++ b/Tests.Integration.Presentation.Web/ItSystem/ItSystemCatalogTests.cs @@ -0,0 +1,107 @@ +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Core.DomainModel.Organization; +using Presentation.Web.Models; +using Tests.Integration.Presentation.Web.Tools; +using Xunit; + +namespace Tests.Integration.Presentation.Web.ItSystem +{ + public class ItSystemCatalogTests : WithAutoFixture + { + + [Theory] + [InlineData(OrganizationRole.User)] + [InlineData(OrganizationRole.GlobalAdmin)] + public async Task Api_Users_Can_Get_IT_System_Data_From_Specific_System_From_own_Organization(OrganizationRole role) + { + //Arrange + var token = await HttpApi.GetTokenAsync(role); + var url = TestEnvironment.CreateUrl($"odata/ItSystems({TestEnvironment.DefaultItSystemId})"); + + //Act + using (var httpResponse = await HttpApi.GetWithTokenAsync(url, token.Token)) + { + var response = await httpResponse.ReadResponseBodyAsAsync(); + //Assert + Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode); + Assert.NotNull(response.Name); + } + } + + [Theory] + [InlineData(OrganizationRole.User, 1)] + [InlineData(OrganizationRole.GlobalAdmin, 2)] + public async Task Api_Users_Can_Get_All_IT_Systems_Data_From_Own_Organizations(OrganizationRole role, int minimumNumberOfItSystems) + { + //Arrange + var token = await HttpApi.GetTokenAsync(role); + + //Act + using (var httpResponse = await HttpApi.GetWithTokenAsync(TestEnvironment.CreateUrl("odata/ItSystems"), token.Token)) + { + var response = await httpResponse.ReadOdataListResponseBodyAsAsync(); + //Assert + Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode); + Assert.NotNull(response.First().Name); + Assert.True(minimumNumberOfItSystems <= response.Count); + } + } + + [Theory] + [InlineData(OrganizationRole.GlobalAdmin, true, true)] + [InlineData(OrganizationRole.LocalAdmin, true, false)] + [InlineData(OrganizationRole.User, true, false)] + public async Task GetAccessRights_Returns(OrganizationRole role, bool canView, bool canCreate) + { + //Arrange + var cookie = await HttpApi.GetCookieAsync(role); + + //Act + using (var httpResponse = await HttpApi.GetWithCookieAsync(TestEnvironment.CreateUrl("api/itsystem?getEntitiesAccessRights=true"), cookie)) + { + //Assert + var response = await httpResponse.ReadResponseBodyAsKitosApiResponseAsync(); + Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode); + Assert.Equal(canView, response.CanView); + Assert.Equal(canCreate, response.CanCreate); + } + } + + [Theory] + [InlineData(OrganizationRole.GlobalAdmin, true, true, true)] + [InlineData(OrganizationRole.LocalAdmin, true, true, true)] //Local admin in own org can delete itsystem + [InlineData(OrganizationRole.User, true, false, false)] + public async Task GetAccessRightsForEntity_Returns(OrganizationRole role, bool canView, bool canEdit, bool canDelete) + { + //Arrange + var cookie = await HttpApi.GetCookieAsync(role); + + //Act + using (var httpResponse = await HttpApi.GetWithCookieAsync(TestEnvironment.CreateUrl($"api/itsystem?id={TestEnvironment.DefaultItSystemId}&getEntityAccessRights=true"), cookie)) + { + //Assert + var response = await httpResponse.ReadResponseBodyAsKitosApiResponseAsync(); + Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode); + Assert.Equal(canView, response.CanView); + Assert.Equal(canEdit, response.CanEdit); + Assert.Equal(canDelete, response.CanDelete); + } + } + + [Fact] + public async Task GetAccessRightsForEntity_With_Unknown_Entity_Returns_404() + { + //Arrange + var cookie = await HttpApi.GetCookieAsync(OrganizationRole.GlobalAdmin); + + //Act + using (var httpResponse = await HttpApi.GetWithCookieAsync(TestEnvironment.CreateUrl($"api/itsystem?id=-1&getEntityAccessRights=true"), cookie)) + { + //Assert + Assert.Equal(HttpStatusCode.NotFound, httpResponse.StatusCode); + } + } + } +} diff --git a/Tests.Integration.Presentation.Web/ItSystem/ItSystemHierarchy.cs b/Tests.Integration.Presentation.Web/ItSystem/ItSystemHierarchy.cs new file mode 100644 index 0000000000..638ba3aa5b --- /dev/null +++ b/Tests.Integration.Presentation.Web/ItSystem/ItSystemHierarchy.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Core.DomainModel.Organization; +using Presentation.Web.Models; +using Tests.Integration.Presentation.Web.Tools; +using Xunit; + +namespace Tests.Integration.Presentation.Web.ItSystem +{ + public class ItSystemHierarchy + { + + [Theory] + [InlineData(OrganizationRole.GlobalAdmin)] + [InlineData(OrganizationRole.User)] + public async Task Api_User_Can_Get_It_System_Hierarchy_Information(OrganizationRole role) + { + var token = await HttpApi.GetTokenAsync(role); + var url = TestEnvironment.CreateUrl($"api/itsystem/{TestEnvironment.DefaultItSystemId}?hierarchy=true"); + + //Act + using (var httpResponse = await HttpApi.GetWithTokenAsync(url, token.Token)) + { + var response = await httpResponse.ReadResponseBodyAsKitosApiResponseAsync>(); + //Assert + Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode); + Assert.NotEmpty(response); + } + } + + [Theory] + [InlineData(OrganizationRole.GlobalAdmin)] + public async Task Api_User_Can_Get_It_System_ParentId(OrganizationRole role) + { + var token = await HttpApi.GetTokenAsync(role); + var url = TestEnvironment.CreateUrl($"api/itsystem/{TestEnvironment.SecondItSystemId}?hierarchy=true"); + + //Act + using (var httpResponse = await HttpApi.GetWithTokenAsync(url, token.Token)) + { + //Assert + var response = await httpResponse.ReadResponseBodyAsKitosApiResponseAsync>(); + Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode); + Assert.NotEmpty(response); + Assert.Equal(TestEnvironment.DefaultItSystemId, response.First(x => x.Id == TestEnvironment.SecondItSystemId).ParentId); + } + } + } +} diff --git a/Tests.Integration.Presentation.Web/ItSystem/ItSystemUsageTests.cs b/Tests.Integration.Presentation.Web/ItSystem/ItSystemUsageTests.cs new file mode 100644 index 0000000000..d06b321fe6 --- /dev/null +++ b/Tests.Integration.Presentation.Web/ItSystem/ItSystemUsageTests.cs @@ -0,0 +1,87 @@ +using System.Net; +using System.Threading.Tasks; +using Core.DomainModel.ItSystemUsage; +using Core.DomainModel.Organization; +using Tests.Integration.Presentation.Web.Tools; +using Xunit; + +namespace Tests.Integration.Presentation.Web.ItSystem +{ + public class ItSystemUsageTests : WithAutoFixture + { + + [Theory] + [InlineData(OrganizationRole.GlobalAdmin)] + [InlineData(OrganizationRole.User)] + public async Task Api_User_Can_Get_All_IT_Systems_In_Use_Data_From_Own_Organization(OrganizationRole role) + { + //Arrange + var token = await HttpApi.GetTokenAsync(role); + var url = TestEnvironment.CreateUrl($"odata/Organizations({TestEnvironment.DefaultOrganizationId})/ItSystemUsages"); + + //Act + using (var httpResponse = await HttpApi.GetWithTokenAsync(url, token.Token)) + { + var response = await httpResponse.ReadOdataListResponseBodyAsAsync(); + //Assert + Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode); + Assert.NotEmpty(response); + } + } + + [Theory] + [InlineData(OrganizationRole.GlobalAdmin)] + [InlineData(OrganizationRole.User)] + public async Task Api_User_Can_Get_All_IT_Systems_In_Use_Data_From_Responsible_OrganizationUnit(OrganizationRole role) + { + //Arrange + var token = await HttpApi.GetTokenAsync(role); + var url = TestEnvironment.CreateUrl($"odata/Organizations({TestEnvironment.DefaultOrganizationId})/OrganizationUnits({TestEnvironment.DefaultOrganizationId})/ItSystemUsages"); + //Act + using (var httpResponse = await HttpApi.GetWithTokenAsync(url, token.Token)) + { + var response = await httpResponse.ReadOdataListResponseBodyAsAsync(); + //Assert + Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode); + Assert.NotEmpty(response); + } + } + + [Fact] + public async Task Api_GlobalAdmin_User_Can_Get_Usages_Across_Organizations() + { + //Arrange + var token = await HttpApi.GetTokenAsync(OrganizationRole.GlobalAdmin); + + //Act + using (var httpResponse = await HttpApi.GetWithTokenAsync(TestEnvironment.CreateUrl("odata/ItSystemUsages"), token.Token)) + { + var response = await httpResponse.ReadOdataListResponseBodyAsAsync(); + //Assert + Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode); + Assert.True(response.Exists(x => x.OrganizationId == TestEnvironment.DefaultOrganizationId)); + Assert.True(response.Exists(x => x.OrganizationId == TestEnvironment.SecondOrganizationId)); + } + } + + [Theory] + [InlineData(OrganizationRole.GlobalAdmin)] + [InlineData(OrganizationRole.User)] + public async Task Api_User_Can_Get_Default_Organization_From_Default_It_System_Usage(OrganizationRole role) + { + //Arrange + var token = await HttpApi.GetTokenAsync(role); + var url = TestEnvironment.CreateUrl( + $"odata/ItSystemUsages({TestEnvironment.DefaultItSystemId})"); + + //Act + using (var httpResponse = await HttpApi.GetWithTokenAsync(url, token.Token)) + { + var response = await httpResponse.ReadResponseBodyAsAsync(); + //Assert + Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode); + Assert.True(response.OrganizationId == TestEnvironment.DefaultOrganizationId); + } + } + } +} diff --git a/Tests.Integration.Presentation.Web/Properties/AssemblyInfo.cs b/Tests.Integration.Presentation.Web/Properties/AssemblyInfo.cs index d261d83ecb..29d7998d23 100644 --- a/Tests.Integration.Presentation.Web/Properties/AssemblyInfo.cs +++ b/Tests.Integration.Presentation.Web/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/Tests.Integration.Presentation.Web/Security/AccessibilityTests.cs b/Tests.Integration.Presentation.Web/Security/AccessibilityTests.cs new file mode 100644 index 0000000000..ddd4061574 --- /dev/null +++ b/Tests.Integration.Presentation.Web/Security/AccessibilityTests.cs @@ -0,0 +1,141 @@ +using System; +using System.Net; +using System.Threading.Tasks; +using Core.DomainModel.ItContract; +using Core.DomainModel.ItProject; +using Core.DomainModel.ItSystem; +using Core.DomainModel.ItSystemUsage; +using Core.DomainModel.Organization; +using Core.DomainModel.Reports; +using Tests.Integration.Presentation.Web.Tools; +using Xunit; +using Tests.Integration.Presentation.Web.Tools.Model; + +namespace Tests.Integration.Presentation.Web.Security +{ + public class AccessibilityTests : WithAutoFixture + { + private readonly string _defaultPassword; + + public AccessibilityTests() + { + _defaultPassword = TestEnvironment.GetDefaultUserPassword(); + } + + [Theory] + [InlineData("api/User", HttpStatusCode.Forbidden)] + [InlineData("api/GlobalAdmin", HttpStatusCode.Forbidden)] + [InlineData("odata/ItSystems", HttpStatusCode.OK)] + public async Task Api_Get_Requests_Using_Token(string apiUrl, HttpStatusCode httpCode) + { + //Arrange + var token = await HttpApi.GetTokenAsync(OrganizationRole.User); + + //Act + using (var httpResponse = await HttpApi.GetWithTokenAsync(TestEnvironment.CreateUrl(apiUrl), token.Token)) + { + //Assert + Assert.Equal(httpCode, httpResponse.StatusCode); + } + } + + [Theory] + [InlineData("api/User", HttpStatusCode.Unauthorized)] + [InlineData("api/GlobalAdmin", HttpStatusCode.Unauthorized)] + [InlineData("odata/ItSystems", HttpStatusCode.Unauthorized)] + public async Task Anonymous_Api_Calls_Returns_401(string apiUrl, HttpStatusCode httpCode) + { + using (var httpResponse = await HttpApi.GetAsync(TestEnvironment.CreateUrl(apiUrl))) + { + Assert.Equal(httpCode, httpResponse.StatusCode); + } + } + + [Theory] + [InlineData("odata/itsystems", typeof(Core.DomainModel.ItSystem.ItSystem))] + [InlineData("api/itsystem", typeof(Core.DomainModel.ItSystem.ItSystem))] + [InlineData("odata/itinterfaces", typeof(ItInterface))] + [InlineData("api/itinterface", typeof(ItInterface))] + [InlineData("odata/reports", typeof(Report))] + [InlineData("api/report", typeof(Report))] + [InlineData("odata/itsystemusages", typeof(ItSystemUsage))] + [InlineData("api/itsystemusage", typeof(ItSystemUsage))] + [InlineData("odata/itcontracts", typeof(ItContract))] + [InlineData("api/itcontract", typeof(ItContract))] + [InlineData("odata/itprojects", typeof(ItProject))] + [InlineData("api/itproject", typeof(ItProject))] + public async Task Api_Is_Read_Only(string path, Type inputType) + { + //Arrange + var globalAdminToken = await HttpApi.GetTokenAsync(OrganizationRole.GlobalAdmin); + + //Act + using (var httpResponse = await HttpApi.PostWithTokenAsync(TestEnvironment.CreateUrl(path), Activator.CreateInstance(inputType), globalAdminToken.Token)) + { + //Assert + Assert.Equal(HttpStatusCode.Forbidden, httpResponse.StatusCode); + var message = await httpResponse.Content.ReadAsStringAsync(); + Assert.Equal("Det er ikke tilladt at skrive data via APIet", message); + } + } + + [Fact] + public async Task Post_Reference_With_Valid_Input_Returns_201() + { + //Arrange + var payload = new + { + Title = A(), + ExternalReferenceId = A(), + URL = "https://strongminds.dk/" + }; + var cookie = await HttpApi.GetCookieAsync(OrganizationRole.User); + + //Act + using (var httpResponse = await HttpApi.PostWithCookieAsync(TestEnvironment.CreateUrl("/api/Reference"), cookie, payload)) + { + //Assert + Assert.Equal(HttpStatusCode.Created, httpResponse.StatusCode); + } + } + + [Fact] + public async Task Token_Can_Be_Invalidated_After_Creation() + { + //Arrange + var email = CreateEmail(); + var userDto = ObjectCreateHelper.MakeSimpleApiUserDto(email, true); + var createdUserId = await HttpApi.CreateOdataUserAsync(userDto, OrganizationRole.User); + var loginDto = ObjectCreateHelper.MakeSimpleLoginDto(email, _defaultPassword); + var token = await HttpApi.GetTokenAsync(loginDto); + using (var requestResponse = await HttpApi.GetWithTokenAsync(TestEnvironment.CreateUrl("api/ItSystem/"), token.Token)) + { + Assert.NotNull(requestResponse); + Assert.Equal(HttpStatusCode.OK, requestResponse.StatusCode); + }; + + //Act + await DisableApiAccessForUserAsync(userDto, createdUserId); + + //Assert + using (var requestResponse = await HttpApi.GetWithTokenAsync(TestEnvironment.CreateUrl("api/ItSystem/"), token.Token)) + { + Assert.NotNull(requestResponse); + Assert.Equal(HttpStatusCode.Forbidden, requestResponse.StatusCode); + }; + await HttpApi.DeleteOdataUserAsync(createdUserId); + } + + private static string CreateEmail() + { + return $"{Guid.NewGuid():N}@test.dk"; + } + + private static async Task DisableApiAccessForUserAsync(ApiUserDTO userDto, int id) + { + userDto.HasApiAccess = false; + await HttpApi.PatchOdataUserAsync(userDto, id); + } + + } +} diff --git a/Tests.Integration.Presentation.Web/Security/ApiAccessibilityTests.cs b/Tests.Integration.Presentation.Web/Security/ApiAccessibilityTests.cs new file mode 100644 index 0000000000..c60749a23b --- /dev/null +++ b/Tests.Integration.Presentation.Web/Security/ApiAccessibilityTests.cs @@ -0,0 +1,63 @@ +using System; +using System.Net; +using System.Threading.Tasks; +using Core.DomainModel.Organization; +using Tests.Integration.Presentation.Web.Tools; +using Tests.Integration.Presentation.Web.Tools.Model; +using Xunit; +using Xunit.Sdk; + +namespace Tests.Integration.Presentation.Web.Security +{ + public class ApiAccessibilityTests : WithAutoFixture + { + + private readonly KitosCredentials _apiUser; + + public ApiAccessibilityTests() + { + _apiUser = TestEnvironment.GetCredentials(OrganizationRole.ApiAccess); + } + + [Fact] + public async Task Can_Access_PublicApi_Endpoint() + { + var role = _apiUser.Role; + + var tokenResponse = await HttpApi.GetTokenAsync(role); + var requestResponse = await HttpApi.GetAsyncWithToken(TestEnvironment.CreateUrl("api/ItSystem/"), tokenResponse.Token); + + Assert.NotNull(requestResponse); + Assert.Equal(HttpStatusCode.OK, requestResponse.StatusCode); + } + + [Fact] + public async Task Can_Not_Access_InternalApi_Endpoint() + { + var role = _apiUser.Role; + + var tokenResponse = await HttpApi.GetTokenAsync(role); + var requestResponse = await HttpApi.GetAsyncWithToken(TestEnvironment.CreateUrl("api/organization/"), tokenResponse.Token); + var contentAsString = await requestResponse.Content.ReadAsStringAsync(); + + Assert.NotNull(requestResponse); + Assert.Equal(HttpStatusCode.Forbidden, requestResponse.StatusCode); + Assert.Equal("Det er ikke tilladt at benytte dette endpoint", contentAsString); + } + + [Fact] + public async Task Can_Not_Access_Odata_Endpoint() + { + var role = _apiUser.Role; + + var tokenResponse = await HttpApi.GetTokenAsync(role); + var requestResponse = await HttpApi.GetAsyncWithToken(TestEnvironment.CreateUrl("odata/Organizations(1)/"), + tokenResponse.Token); + var contentAsString = await requestResponse.Content.ReadAsStringAsync(); + + Assert.NotNull(requestResponse); + Assert.Equal(HttpStatusCode.Forbidden, requestResponse.StatusCode); + Assert.Equal("Det er ikke tilladt at kalde odata endpoints", contentAsString); + } + } +} diff --git a/Tests.Integration.Presentation.Web/Security/AuthorizationTests.cs b/Tests.Integration.Presentation.Web/Security/AuthorizationTests.cs new file mode 100644 index 0000000000..3bcd1749d8 --- /dev/null +++ b/Tests.Integration.Presentation.Web/Security/AuthorizationTests.cs @@ -0,0 +1,84 @@ +using System; +using System.Net; +using System.Threading.Tasks; +using Core.DomainModel.Organization; +using Tests.Integration.Presentation.Web.Tools; +using Tests.Integration.Presentation.Web.Tools.Model; +using Xunit; + +namespace Tests.Integration.Presentation.Web.Security +{ + public class AuthorizationTests : WithAutoFixture + { + private readonly KitosCredentials _regularApiUser, _globalAdmin; + private readonly Uri _getTokenUrl; + + public AuthorizationTests() + { + _regularApiUser = TestEnvironment.GetCredentials(OrganizationRole.User, true); + _globalAdmin = TestEnvironment.GetCredentials(OrganizationRole.GlobalAdmin); + _getTokenUrl = TestEnvironment.CreateUrl("api/authorize/GetToken"); + } + + [Fact] + public async Task Api_Access_User_Can_Get_Token() + { + //Arrange + var loginDto = ObjectCreateHelper.MakeSimpleLoginDto(_regularApiUser.Username, _regularApiUser.Password); + + //Act + var tokenResponse = await HttpApi.GetTokenAsync(loginDto); + + //Assert + Assert.NotNull(tokenResponse); + Assert.True(tokenResponse.LoginSuccessful); + Assert.True(tokenResponse.Expires > DateTime.UtcNow); + Assert.False(string.IsNullOrWhiteSpace(tokenResponse.Token)); + } + + [Fact] + public async Task User_Without_Api_Access_Can_Not_Get_Token() + { + //Arrange + var role = _globalAdmin.Role; + var userCredentials = TestEnvironment.GetCredentials(role); + var url = TestEnvironment.CreateUrl("api/authorize/GetToken"); + var loginDto = ObjectCreateHelper.MakeSimpleLoginDto(userCredentials.Username, userCredentials.Password); + + //Act + var tokenResponse = await HttpApi.PostAsync(url, loginDto); + + //Assert + Assert.Equal(HttpStatusCode.Forbidden, tokenResponse.StatusCode); + } + + [Fact] + public async Task Get_Token_Returns_401_On_Invalid_Password() + { + //Arrange + var loginDto = ObjectCreateHelper.MakeSimpleLoginDto(_regularApiUser.Username, A()); + + //Act + using (var httpResponseMessage = await HttpApi.PostAsync(_getTokenUrl, loginDto)) + { + //Assert + Assert.Equal(HttpStatusCode.Unauthorized, httpResponseMessage.StatusCode); + } + } + + [Fact] + public async Task Get_Token_Returns_401_On_Invalid_Username() + { + //Arrange + var loginDto = ObjectCreateHelper.MakeSimpleLoginDto(A(), _regularApiUser.Password); + + //Act + using (var httpResponseMessage = await HttpApi.PostAsync(_getTokenUrl, loginDto)) + { + //Assert + Assert.Equal(HttpStatusCode.Unauthorized, httpResponseMessage.StatusCode); + } + } + + } +} diff --git a/Tests.Integration.Presentation.Web/Swagger/SwaggerDocumentationTest.cs b/Tests.Integration.Presentation.Web/Swagger/SwaggerDocumentationTest.cs new file mode 100644 index 0000000000..ef6e12c00b --- /dev/null +++ b/Tests.Integration.Presentation.Web/Swagger/SwaggerDocumentationTest.cs @@ -0,0 +1,33 @@ +using System.Net; +using System.Threading.Tasks; +using Tests.Integration.Presentation.Web.Tools; +using Xunit; + +namespace Tests.Integration.Presentation.Web.Swagger +{ + public class SwaggerDocumentationTest + { + public class SwaggerDoc + { + public string Swagger { get; set; } + public string Host { get; set; } + } + + [Fact] + public async Task Can_Load_Swagger_Doc() + { + //Arrange + var url = TestEnvironment.CreateUrl("/swagger/docs/1.0.0"); + + //Act + using (var result = await HttpApi.GetAsync(url)) + { + //Assert + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + var doc = await result.ReadResponseBodyAsAsync(); + Assert.Equal("2.0", doc.Swagger); + Assert.Equal(url.Host, doc.Host); + } + } + } +} diff --git a/Tests.Integration.Presentation.Web/TestEnvironmentValidation/TestEnvironmentTest.cs b/Tests.Integration.Presentation.Web/TestEnvironmentValidation/TestEnvironmentTest.cs index 881cb0e2b8..8a67609fa3 100644 --- a/Tests.Integration.Presentation.Web/TestEnvironmentValidation/TestEnvironmentTest.cs +++ b/Tests.Integration.Presentation.Web/TestEnvironmentValidation/TestEnvironmentTest.cs @@ -10,9 +10,11 @@ public class TestEnvironmentTest [InlineData(OrganizationRole.User)] [InlineData(OrganizationRole.LocalAdmin)] [InlineData(OrganizationRole.GlobalAdmin)] - public void User_With_Role_Is_Available(OrganizationRole role) + [InlineData(OrganizationRole.User, true)] + [InlineData(OrganizationRole.GlobalAdmin, true)] + public void User_With_Role_Is_Available(OrganizationRole role, bool apiAccess = false) { - var user = TestEnvironment.GetCredentials(role); + var user = TestEnvironment.GetCredentials(role, apiAccess); Assert.NotNull(user); Assert.False(string.IsNullOrWhiteSpace(user.Username)); diff --git a/Tests.Integration.Presentation.Web/Tests.Integration.Presentation.Web.csproj b/Tests.Integration.Presentation.Web/Tests.Integration.Presentation.Web.csproj index a4da574244..d8ee59d421 100644 --- a/Tests.Integration.Presentation.Web/Tests.Integration.Presentation.Web.csproj +++ b/Tests.Integration.Presentation.Web/Tests.Integration.Presentation.Web.csproj @@ -1,5 +1,6 @@  + @@ -34,8 +35,31 @@ 4 + + ..\packages\AutoFixture.4.11.0\lib\net452\AutoFixture.dll + + + ..\packages\Castle.Core.4.4.0\lib\net45\Castle.Core.dll + + + ..\packages\Fare.2.1.1\lib\net35\Fare.dll + + + ..\packages\Moq.4.12.0\lib\net45\Moq.dll + + + ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll + + + + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + + + ..\packages\System.Threading.Tasks.Extensions.4.5.1\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll + @@ -43,35 +67,52 @@ - ..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll + ..\packages\xunit.abstractions.2.0.3\lib\net35\xunit.abstractions.dll True - - ..\packages\xunit.assert.2.1.0\lib\dotnet\xunit.assert.dll + + ..\packages\xunit.assert.2.4.1\lib\netstandard1.1\xunit.assert.dll - - ..\packages\xunit.extensibility.core.2.1.0\lib\dotnet\xunit.core.dll + + ..\packages\xunit.extensibility.core.2.4.1\lib\net452\xunit.core.dll - - ..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll + + ..\packages\xunit.extensibility.execution.2.4.1\lib\net452\xunit.execution.desktop.dll - - ..\packages\xunit.runner.reporters.2.1.0\lib\net45\xunit.runner.reporters.desktop.dll + + ..\packages\xunit.runner.reporters.2.2.0\lib\net452\xunit.runner.reporters.net452.dll - - ..\packages\xunit.runner.utility.2.1.0\lib\net35\xunit.runner.utility.desktop.dll - True + + ..\packages\xunit.runner.utility.2.2.0\lib\net452\xunit.runner.utility.net452.dll + + + + + + + + + + + + + + + - + + + + @@ -79,6 +120,10 @@ {A76A8E41-74F7-4443-A5F3-059B5414D83B} Core.DomainModel + + {E75385A3-EA7C-4DFF-B989-BEE64BC506ED} + Presentation.Web + @@ -86,5 +131,8 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + \ No newline at end of file diff --git a/Tests.Integration.Presentation.Web/Tools/HttpApi.cs b/Tests.Integration.Presentation.Web/Tools/HttpApi.cs new file mode 100644 index 0000000000..1941e24380 --- /dev/null +++ b/Tests.Integration.Presentation.Web/Tools/HttpApi.cs @@ -0,0 +1,235 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using Core.DomainModel.Organization; +using Newtonsoft.Json; +using Presentation.Web.Models; +using Tests.Integration.Presentation.Web.Tools.Model; +using Xunit; + +namespace Tests.Integration.Presentation.Web.Tools +{ + public static class HttpApi + { + /// + /// Use for stateless calls only + /// + private static readonly HttpClient StatelessHttpClient = + new HttpClient( + new HttpClientHandler + { + UseCookies = false + }); + + public static Task GetWithTokenAsync(Uri url, string token) + { + var requestMessage = new HttpRequestMessage(HttpMethod.Get, url); + requestMessage.Headers.Authorization = AuthenticationHeaderValue.Parse("bearer " + token); + return StatelessHttpClient.SendAsync(requestMessage); + } + + public static Task PostWithTokenAsync(Uri url, object body, string token) + { + var requestMessage = CreatePostMessage(url, body); + requestMessage.Headers.Authorization = AuthenticationHeaderValue.Parse("bearer " + token); + return StatelessHttpClient.SendAsync(requestMessage); + } + + public static Task PostWithCookieAsync(Uri url, Cookie cookie, object body) + { + var requestMessage = new HttpRequestMessage(HttpMethod.Post, url) + { + Content = new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json") + }; + + return SendWithCookieAsync(cookie, requestMessage); + } + + public static Task GetWithCookieAsync(Uri url, Cookie cookie) + { + var requestMessage = new HttpRequestMessage(HttpMethod.Get, url); + + return SendWithCookieAsync(cookie, requestMessage); + } + + public static Task DeleteWithCookieAsync(Uri url, Cookie cookie) + { + var requestMessage = new HttpRequestMessage(HttpMethod.Delete, url); + + return SendWithCookieAsync(cookie, requestMessage); + } + + public static Task PatchWithCookieAsync(Uri url, Cookie cookie, object body) + { + var requestMessage = new HttpRequestMessage(new HttpMethod("PATCH"), url) + { + Content = new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json") + }; + + return SendWithCookieAsync(cookie, requestMessage); + } + + private static async Task SendWithCookieAsync(Cookie cookie, HttpRequestMessage requestMessage) + { + //Make sure state does not bleed into stateless handler + var cookieContainer = new CookieContainer(); + cookieContainer.Add(cookie); + using (var client = new HttpClient(new HttpClientHandler {CookieContainer = cookieContainer})) + { + return await client.SendAsync(requestMessage); + } + } + + public static Task PostAsync(Uri url, object body) + { + var requestMessage = CreatePostMessage(url, body); + return StatelessHttpClient.SendAsync(requestMessage); + } + + private static HttpRequestMessage CreatePostMessage(Uri url, object body) + { + var requestMessage = new HttpRequestMessage(HttpMethod.Post, url) + { + Content = new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json") + }; + return requestMessage; + } + + public static Task GetAsync(Uri url) + { + var requestMessage = new HttpRequestMessage(HttpMethod.Get, url); + return StatelessHttpClient.SendAsync(requestMessage); + } + + public static async Task ReadResponseBodyAsAsync(this HttpResponseMessage response) + { + var responseAsJson = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + return JsonConvert.DeserializeObject(responseAsJson); + } + + public static async Task> ReadOdataListResponseBodyAsAsync(this HttpResponseMessage response) + { + var responseAsJson = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + var spec = new { value = new List() }; + var result = JsonConvert.DeserializeAnonymousType(responseAsJson, spec); + return result.value; + } + + public static async Task ReadResponseBodyAsKitosApiResponseAsync(this HttpResponseMessage response) + { + var apiReturnFormat = await response.ReadResponseBodyAsAsync>().ConfigureAwait(false); + return apiReturnFormat.Response; + } + + public static async Task GetTokenAsync(OrganizationRole role) + { + var url = TestEnvironment.CreateUrl("api/authorize/GetToken"); + var userCredentials = TestEnvironment.GetCredentials(role, true); + var loginDto = ObjectCreateHelper.MakeSimpleLoginDto(userCredentials.Username, userCredentials.Password); + + using (var httpResponseMessage = await PostAsync(url, loginDto)) + { + return await GetTokenResponseDtoAsync(loginDto, httpResponseMessage); + } + } + + public static async Task GetTokenAsync(LoginDTO loginDto) + { + var url = TestEnvironment.CreateUrl("api/authorize/GetToken"); + + using (var httpResponseMessage = await PostAsync(url, loginDto)) + { + return await GetTokenResponseDtoAsync(loginDto, httpResponseMessage); + } + } + + private static async Task GetTokenResponseDtoAsync(LoginDTO loginDto, HttpResponseMessage httpResponseMessage) + { + Assert.Equal(HttpStatusCode.OK, httpResponseMessage.StatusCode); + var tokenResponse = await httpResponseMessage.ReadResponseBodyAsKitosApiResponseAsync() + .ConfigureAwait(false); + + Assert.Equal(loginDto.Email, tokenResponse.Email); + Assert.True(tokenResponse.LoginSuccessful); + Assert.True(tokenResponse.Expires > DateTime.UtcNow); + Assert.False(string.IsNullOrWhiteSpace(tokenResponse.Token)); + + return tokenResponse; + } + + public static async Task GetCookieAsync(OrganizationRole role) + { + var userCredentials = TestEnvironment.GetCredentials(role); + var url = TestEnvironment.CreateUrl("api/authorize"); + var loginDto = ObjectCreateHelper.MakeSimpleLoginDto(userCredentials.Username, userCredentials.Password); + + using (var client = new HttpClient()) + { + var request = CreatePostMessage(url,loginDto); + var cookieResponse = await client.SendAsync(request); + var cookieParts = cookieResponse.Headers.First(x => x.Key == "Set-Cookie").Value.First().Split('='); + var cookieName = cookieParts[0]; + var cookieValue = cookieParts[1].Split(';')[0]; + + return new Cookie(cookieName, cookieValue) + { + Domain = url.Host + }; + } + } + + public static async Task CreateOdataUserAsync(ApiUserDTO userDto, OrganizationRole role, int organizationId = 1) + { + var cookie = await GetCookieAsync(OrganizationRole.GlobalAdmin); + + var createUserDto = ObjectCreateHelper.MakeSimpleCreateUserDto(userDto); + + int userId; + using (var createdResponse = await PostWithCookieAsync(TestEnvironment.CreateUrl("odata/Users/Users.Create"), cookie, createUserDto)) + { + Assert.Equal(HttpStatusCode.Created, createdResponse.StatusCode); + var response = await createdResponse.ReadResponseBodyAsAsync(); + userId = response.Id; + + Assert.Equal(userDto.Email, response.Email); + } + + var roleDto = new OrgRightDTO + { + UserId = userId, + Role = role.ToString("G") + }; + + using (var addedRole = await PostWithCookieAsync(TestEnvironment.CreateUrl($"odata/Organizations({organizationId})/Rights"), cookie, roleDto)) + { + Assert.Equal(HttpStatusCode.Created, addedRole.StatusCode); + } + + return userId; + } + + public static async Task PatchOdataUserAsync(ApiUserDTO userDto, int userId) + { + var cookie = await GetCookieAsync(OrganizationRole.GlobalAdmin); + + using (var patch = await PatchWithCookieAsync(TestEnvironment.CreateUrl($"odata/Users({userId})"), cookie, userDto)) + { + Assert.Equal(HttpStatusCode.NoContent, patch.StatusCode); + return patch; + }; + } + + public static async Task DeleteOdataUserAsync(int id) + { + var cookie = await GetCookieAsync(OrganizationRole.GlobalAdmin); + var response = await DeleteWithCookieAsync(TestEnvironment.CreateUrl("api/user/" + id), cookie); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + return response; + } + } +} diff --git a/Tests.Integration.Presentation.Web/Tools/InterfaceHelper.cs b/Tests.Integration.Presentation.Web/Tools/InterfaceHelper.cs new file mode 100644 index 0000000000..d36318d1e3 --- /dev/null +++ b/Tests.Integration.Presentation.Web/Tools/InterfaceHelper.cs @@ -0,0 +1,69 @@ +using System.Net; +using System.Threading.Tasks; +using Core.DomainModel; +using Core.DomainModel.Organization; +using Presentation.Web.Models; +using Xunit; + +namespace Tests.Integration.Presentation.Web.Tools +{ + public static class InterfaceHelper + { + public static ItInterfaceDTO CreateInterfaceDto( + string name, + string interfaceId, + int? userId, + int orgId, + AccessModifier access) + { + return new ItInterfaceDTO + { + ItInterfaceId = interfaceId, + Name = name, + OrganizationId = orgId, + BelongsToId = userId, + AccessModifier = access + }; + } + public static async Task CreateInterface(ItInterfaceDTO input) + { + var cookie = await HttpApi.GetCookieAsync(OrganizationRole.GlobalAdmin); + var url = TestEnvironment.CreateUrl("api/itinterface"); + + using (var createdResponse = await HttpApi.PostWithCookieAsync(url, cookie, input)) + { + Assert.Equal(HttpStatusCode.Created, createdResponse.StatusCode); + return await createdResponse.ReadResponseBodyAsKitosApiResponseAsync(); + } + } + + public static async Task CreateInterfaces(params ItInterfaceDTO[] interfaces) + { + foreach (var dto in interfaces) + { + await CreateInterface(dto); + } + } + + public static async Task CreateItInterfaceUsageAsync(int itSystemUsageId, int interfaceId, int itSystemId, int organizationId, int contractId) + { + var cookie = await HttpApi.GetCookieAsync(OrganizationRole.GlobalAdmin); + + var url = TestEnvironment.CreateUrl($"api/ItInterfaceUsage?usageId={itSystemUsageId}&interfaceId={interfaceId}&sysId={itSystemId}&organizationId={organizationId}"); + var body = new + { + itContractId = contractId + }; + + using (var createdResponse = await HttpApi.PatchWithCookieAsync(url, cookie, body)) + { + Assert.Equal(HttpStatusCode.OK, createdResponse.StatusCode); + var response = await createdResponse.ReadResponseBodyAsKitosApiResponseAsync(); + + Assert.Equal(itSystemUsageId, response.ItSystemUsageId); + Assert.Equal(interfaceId, response.ItInterfaceId); + Assert.Equal(itSystemId, response.ItSystemId); + } + } + } +} diff --git a/Tests.Integration.Presentation.Web/Tools/Model/ApiUserDTO.cs b/Tests.Integration.Presentation.Web/Tools/Model/ApiUserDTO.cs new file mode 100644 index 0000000000..9bd0f2b56a --- /dev/null +++ b/Tests.Integration.Presentation.Web/Tools/Model/ApiUserDTO.cs @@ -0,0 +1,13 @@ +namespace Tests.Integration.Presentation.Web.Tools.Model +{ + public class ApiUserDTO + { + public string Name { get; set; } + + public string LastName { get; set; } + + public string Email { get; set; } + + public bool? HasApiAccess { get; set; } + } +} diff --git a/Tests.Integration.Presentation.Web/Tools/Model/CreateUserDTO.cs b/Tests.Integration.Presentation.Web/Tools/Model/CreateUserDTO.cs new file mode 100644 index 0000000000..0dff47ee01 --- /dev/null +++ b/Tests.Integration.Presentation.Web/Tools/Model/CreateUserDTO.cs @@ -0,0 +1,11 @@ +namespace Tests.Integration.Presentation.Web.Tools.Model +{ + public class CreateUserDTO + { + public ApiUserDTO user { get; set; } + + public int organizationId { get; set; } + + public bool sendMailOnCreation { get; set; } + } +} diff --git a/Tests.Integration.Presentation.Web/Tools/Model/OrgRightDTO.cs b/Tests.Integration.Presentation.Web/Tools/Model/OrgRightDTO.cs new file mode 100644 index 0000000000..3cf126c932 --- /dev/null +++ b/Tests.Integration.Presentation.Web/Tools/Model/OrgRightDTO.cs @@ -0,0 +1,8 @@ +namespace Tests.Integration.Presentation.Web.Tools.Model +{ + class OrgRightDTO + { + public int UserId { get; set; } + public string Role { get; set; } + } +} diff --git a/Tests.Integration.Presentation.Web/Tools/ObjectCreateHelper.cs b/Tests.Integration.Presentation.Web/Tools/ObjectCreateHelper.cs new file mode 100644 index 0000000000..130cf42539 --- /dev/null +++ b/Tests.Integration.Presentation.Web/Tools/ObjectCreateHelper.cs @@ -0,0 +1,41 @@ +using Presentation.Web.Models; +using Tests.Integration.Presentation.Web.Tools.Model; +using AutoFixture; + +namespace Tests.Integration.Presentation.Web.Tools +{ + public class ObjectCreateHelper + { + private static readonly Fixture Fixture = new Fixture(); + + public static LoginDTO MakeSimpleLoginDto(string email, string pwd) + { + return new LoginDTO + { + Email = email, + Password = pwd + }; + } + + public static ApiUserDTO MakeSimpleApiUserDto(string email, bool apiAccess) + { + return new ApiUserDTO + { + Email = email, + Name = Fixture.Create(), + LastName = Fixture.Create(), + HasApiAccess = apiAccess + }; + } + + public static CreateUserDTO MakeSimpleCreateUserDto(ApiUserDTO apiUser) + { + return new CreateUserDTO + { + user = apiUser, + organizationId = TestEnvironment.DefaultOrganizationId, + sendMailOnCreation = false + }; + } + } +} diff --git a/Tests.Integration.Presentation.Web/Tools/TestEnvironment.cs b/Tests.Integration.Presentation.Web/Tools/TestEnvironment.cs index 80ca24d4c5..729d035947 100644 --- a/Tests.Integration.Presentation.Web/Tools/TestEnvironment.cs +++ b/Tests.Integration.Presentation.Web/Tools/TestEnvironment.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; +using System.Configuration; using Core.DomainModel.Organization; using Tests.Integration.Presentation.Web.Tools.Model; @@ -9,7 +9,16 @@ namespace Tests.Integration.Presentation.Web.Tools public static class TestEnvironment { private static readonly IReadOnlyDictionary UsersFromEnvironment; + private static readonly IReadOnlyDictionary ApiUsersFromEnvironment; private static readonly KitosTestEnvironment ActiveEnvironment; + private static readonly string DefaultUserPassword; + public const int DefaultItSystemId = 1; + public const int DefaultItSystemUsageId = 1; + public const int SecondItSystemId = 2; + public const int DefaultOrganizationId = 1; + public const int SecondOrganizationId = 2; + public const int DefaultContractId = 1; + public const int DefaultUserId = 1; static TestEnvironment() { @@ -27,55 +36,88 @@ static TestEnvironment() { //Expecting the following users to be available to local testing Console.Out.WriteLine("Running locally. Loading all configuration in-line"); + const string localDevUserPassword = "localNoSecret"; + DefaultUserPassword = "arne123"; UsersFromEnvironment = new Dictionary { { OrganizationRole.User, new KitosCredentials( - "local-regular-user@strongminds.dk", - "localNoSecret", + "local-regular-user@kitos.dk", + localDevUserPassword, OrganizationRole.User) }, { OrganizationRole.LocalAdmin, new KitosCredentials( - "local-local-admin-user@strongminds.dk", - "localNoSecret", + "local-local-admin-user@kitos.dk", + localDevUserPassword, OrganizationRole.LocalAdmin) }, { OrganizationRole.GlobalAdmin, new KitosCredentials( - "local-global-admin-user@strongminds.dk", - "localNoSecret", + "local-global-admin-user@kitos.dk", + localDevUserPassword, OrganizationRole.GlobalAdmin) } }; + ApiUsersFromEnvironment = new Dictionary + { + { + OrganizationRole.User, + new KitosCredentials( + "local-api-user@kitos.dk", + localDevUserPassword, + OrganizationRole.User) + }, + { + OrganizationRole.GlobalAdmin, + new KitosCredentials( + "local-api-global-admin-user@kitos.dk", + localDevUserPassword, + OrganizationRole.GlobalAdmin) + } + }; + } else { //Loading users from environment Console.Out.WriteLine("Tests running towards remote target. Loading configuration from environment."); + DefaultUserPassword = GetEnvironmentVariable("DefaultUserPassword"); UsersFromEnvironment = new Dictionary { {OrganizationRole.User, LoadUserFromEnvironment(OrganizationRole.User)}, {OrganizationRole.LocalAdmin, LoadUserFromEnvironment(OrganizationRole.LocalAdmin)}, {OrganizationRole.GlobalAdmin, LoadUserFromEnvironment(OrganizationRole.GlobalAdmin)} }; + ApiUsersFromEnvironment = new Dictionary + { + + {OrganizationRole.User, LoadUserFromEnvironment(OrganizationRole.User, true)}, + {OrganizationRole.GlobalAdmin, LoadUserFromEnvironment(OrganizationRole.GlobalAdmin, true)} + }; } } - private static KitosCredentials LoadUserFromEnvironment(OrganizationRole role) + private static KitosCredentials LoadUserFromEnvironment(OrganizationRole role, bool apiAccess = false) { var suffix = string.Empty; switch (role) { + case OrganizationRole.User when apiAccess: + suffix = "ApiUser"; + break; case OrganizationRole.User: suffix = "NormalUser"; break; case OrganizationRole.LocalAdmin: suffix = "LocalAdmin"; break; + case OrganizationRole.GlobalAdmin when apiAccess: + suffix = "ApiGlobalAdmin"; + break; case OrganizationRole.GlobalAdmin: suffix = "GlobalAdmin"; break; @@ -106,13 +148,38 @@ private static string GetEnvironmentVariable(string name, bool mandatory = true, return variable; } - public static KitosCredentials GetCredentials(OrganizationRole role) + public static KitosCredentials GetCredentials(OrganizationRole role, bool apiAccess = false) { - if (UsersFromEnvironment.TryGetValue(role, out var credentials)) + var userEnvironment = apiAccess ? ApiUsersFromEnvironment : UsersFromEnvironment; + + if (userEnvironment.TryGetValue(role, out var credentials)) { return credentials; } - throw new ArgumentNullException($"No environment user configured for role:{role:G}"); + throw new ArgumentNullException($"No environment {(apiAccess ? "api " : "")}user configured for role:{role:G}"); + } + + public static string GetBaseUrl() + { + switch (ActiveEnvironment) + { + case KitosTestEnvironment.Local: + return "https://localhost:44300"; + case KitosTestEnvironment.Integration: + return $"https://{GetEnvironmentVariable("KitosHostName")}"; + default: + throw new ArgumentOutOfRangeException(); + } + } + + public static Uri CreateUrl(string pathAndQuery) + { + return new Uri($"{GetBaseUrl()}/{pathAndQuery.TrimStart('/')}"); + } + + public static string GetDefaultUserPassword() + { + return DefaultUserPassword; } } } diff --git a/Tests.Integration.Presentation.Web/Tools/WithAutoFixture.cs b/Tests.Integration.Presentation.Web/Tools/WithAutoFixture.cs new file mode 100644 index 0000000000..cca62ec3d2 --- /dev/null +++ b/Tests.Integration.Presentation.Web/Tools/WithAutoFixture.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using AutoFixture; + +namespace Tests.Integration.Presentation.Web.Tools +{ + public abstract class WithAutoFixture + { + private readonly Fixture _fixture; + + protected WithAutoFixture() + { + _fixture = new Fixture(); + OnFixtureCreated(_fixture); + } + + protected virtual void OnFixtureCreated(Fixture fixture) + { + //Override to configure fixture-specific defaults + } + + protected T A() + { + return _fixture.Create(); + } + + protected IEnumerable Many(int? howMany = null) + { + return howMany.HasValue ? _fixture.CreateMany(howMany.Value) : _fixture.CreateMany(); + } + } +} diff --git a/Tests.Integration.Presentation.Web/app.config b/Tests.Integration.Presentation.Web/app.config new file mode 100644 index 0000000000..1939547f0a --- /dev/null +++ b/Tests.Integration.Presentation.Web/app.config @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests.Integration.Presentation.Web/packages.config b/Tests.Integration.Presentation.Web/packages.config index 6a29a76a4f..ef63c4c3fb 100644 --- a/Tests.Integration.Presentation.Web/packages.config +++ b/Tests.Integration.Presentation.Web/packages.config @@ -1,13 +1,19 @@  - - - - - - + + + + + + + + + + + + - - + + \ No newline at end of file diff --git a/Tests.Unit.Core.ApplicationServices/ApplicationServices/AuthenticationServiceTest.cs b/Tests.Unit.Core.ApplicationServices/ApplicationServices/AuthenticationServiceTest.cs index ae7d5266db..b2642d050e 100644 --- a/Tests.Unit.Core.ApplicationServices/ApplicationServices/AuthenticationServiceTest.cs +++ b/Tests.Unit.Core.ApplicationServices/ApplicationServices/AuthenticationServiceTest.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Linq.Expressions; -using System.Net; using Core.ApplicationServices; using Core.DomainModel; using Core.DomainModel.ItContract; @@ -14,20 +12,17 @@ using Core.DomainServices; using FluentAssertions; using NSubstitute; -using Tests.Unit.Presentation.Web.Helpers; using Xunit; -//https://datatellblog.wordpress.com/2015/05/05/unit-testing-asp-net-mvc-authorization/ - namespace Tests.Unit.Core.ApplicationServices { public class AuthenticationServiceTest { private IGenericRepository _userRepository; - private IGenericRepository _itContractRepository; private IGenericRepository _organizationUnitRepository; private AuthenticationService _authenticationService; private IFeatureChecker _featureChecker; + private IOrganizationRoleService _organizationRoleService; public AuthenticationServiceTest() { @@ -36,10 +31,10 @@ public AuthenticationServiceTest() private void SetUp() { - _itContractRepository = Substitute.For>(); _organizationUnitRepository = Substitute.For>(); _userRepository = Substitute.For>(); - _featureChecker = new FeatureChecker(); + _organizationRoleService = Substitute.For(); + _featureChecker = new FeatureChecker(_organizationRoleService); _authenticationService = new AuthenticationService(_userRepository, _featureChecker); IQueryable organizationUnits = new EnumerableQuery(new List()); _organizationUnitRepository.AsQueryable().Returns(organizationUnits); @@ -169,7 +164,7 @@ public void test_has_write_access() private Entity SetOwner(Entity entity, User owner) { - if(entity is Organization) + if (entity is Organization) entity.Id = owner.DefaultOrganizationId.GetValueOrDefault(); else ((IHasOrganization)entity).OrganizationId = owner.DefaultOrganizationId.GetValueOrDefault(); @@ -192,9 +187,9 @@ private Report CreateReport(User owner) }; } - private static User CreateTestUser(int orgKey, bool isGlobalmin = false, OrganizationRole role = OrganizationRole.User, OrganizationCategory organizationCategory = OrganizationCategory.Other, int userId = 1) + private User CreateTestUser(int orgKey, bool isGlobalmin = false, OrganizationRole role = OrganizationRole.User, OrganizationCategory organizationCategory = OrganizationCategory.Other, int userId = 1) { - return new User + var user = new User { Id = userId, IsGlobalAdmin = isGlobalmin, @@ -206,10 +201,18 @@ private static User CreateTestUser(int orgKey, bool isGlobalmin = false, Organiz Rights = new List { new OrganizationRight { OrganizationId = orgKey, Role = role } } }, OrganizationRights = new List - { - new OrganizationRight { OrganizationId = orgKey, Role = role } - } + { + new OrganizationRight { OrganizationId = orgKey, Role = role } + } }; + var roleList = new List { role }; + if (isGlobalmin) + { + roleList.Add(OrganizationRole.GlobalAdmin); + } + + _organizationRoleService.GetRolesInOrganization(user, Arg.Any()).Returns(roleList); + return user; } private User SetAccess(bool allow, int orgKey, bool isGlobalmin = false, OrganizationRole role = OrganizationRole.User, OrganizationCategory organizationCategory = OrganizationCategory.Other) @@ -219,6 +222,7 @@ private User SetAccess(bool allow, int orgKey, bool isGlobalmin = false, Organiz if (allow) { var user = CreateTestUser(orgKey, isGlobalmin, role, organizationCategory); + list.Add(user); } diff --git a/Tests.Unit.Core.ApplicationServices/Tests.Unit.Core.csproj b/Tests.Unit.Core.ApplicationServices/Tests.Unit.Core.csproj index aea1c908ba..9b7c911958 100644 --- a/Tests.Unit.Core.ApplicationServices/Tests.Unit.Core.csproj +++ b/Tests.Unit.Core.ApplicationServices/Tests.Unit.Core.csproj @@ -39,24 +39,6 @@ - - bin\Test\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\Prod\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - ..\packages\FluentAssertions.4.17.0\lib\net45\FluentAssertions.dll @@ -74,17 +56,14 @@ ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.0.0\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll True - - ..\packages\Microsoft.OData.Core.6.15.0\lib\portable-net45+win+wpa81\Microsoft.OData.Core.dll - True + + ..\packages\Microsoft.OData.Core.7.0.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Core.dll - - ..\packages\Microsoft.OData.Edm.6.15.0\lib\portable-net45+win+wpa81\Microsoft.OData.Edm.dll - True + + ..\packages\Microsoft.OData.Edm.7.0.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Edm.dll - - ..\packages\Microsoft.Spatial.6.15.0\lib\portable-net45+win+wpa81\Microsoft.Spatial.dll - True + + ..\packages\Microsoft.Spatial.7.0.0\lib\portable-net45+win8+wpa81\Microsoft.Spatial.dll ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll @@ -104,9 +83,8 @@ ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll True - - ..\packages\Microsoft.AspNet.OData.5.9.1\lib\net45\System.Web.OData.dll - True + + ..\packages\Microsoft.AspNet.OData.6.0.0\lib\net45\System.Web.OData.dll @@ -148,7 +126,9 @@ - + + Designer + @@ -167,6 +147,7 @@ + diff --git a/Tests.Unit.Core.ApplicationServices/packages.config b/Tests.Unit.Core.ApplicationServices/packages.config index a4cab080ce..593e96d8b3 100644 --- a/Tests.Unit.Core.ApplicationServices/packages.config +++ b/Tests.Unit.Core.ApplicationServices/packages.config @@ -1,14 +1,14 @@  - + - - - + + + diff --git a/Tests.Unit.Presentation.Web/Authorization/ContextBasedAuthorizationStrategyTest.cs b/Tests.Unit.Presentation.Web/Authorization/ContextBasedAuthorizationStrategyTest.cs new file mode 100644 index 0000000000..1e88f56eb1 --- /dev/null +++ b/Tests.Unit.Presentation.Web/Authorization/ContextBasedAuthorizationStrategyTest.cs @@ -0,0 +1,99 @@ +using Core.ApplicationServices.Authorization; +using Core.DomainModel; +using Core.DomainServices.Authorization; +using Moq; +using Presentation.Web.Infrastructure.Authorization.Controller; +using Tests.Unit.Presentation.Web.Helpers; +using Xunit; + +namespace Tests.Unit.Presentation.Web.Authorization +{ + public class ContextBasedAuthorizationStrategyTest : WithAutoFixture + { + private readonly Mock _authContext; + private readonly ContextBasedAuthorizationStrategy _sut; + + public ContextBasedAuthorizationStrategyTest() + { + _authContext = new Mock(); + _sut = new ContextBasedAuthorizationStrategy(_authContext.Object); + } + + [Fact] + public void GetCrossOrganizationReadAccess_Returns_Result_From_Context() + { + //Arrange + var expectedResult = A(); + _authContext.Setup(x => x.GetCrossOrganizationReadAccess()).Returns(expectedResult); + + //Act + var result = _sut.GetCrossOrganizationReadAccess(); + + //Assert + Assert.Equal(expectedResult, result); + } + + [Fact] + public void GetOrganizationReadAccessLevel_Returns_Result_From_Context() + { + //Arrange + var expectedResult = A(); + var organizationId = A(); + _authContext.Setup(x => x.GetOrganizationReadAccessLevel(organizationId)).Returns(expectedResult); + + //Act + var result = _sut.GetOrganizationReadAccessLevel(organizationId); + + //Assert + Assert.Equal(expectedResult, result); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void AllowReadAccess_Returns_Response_From_Context(bool expectedResult) + { + //Arrange + var entity = Mock.Of(); + _authContext.Setup(x => x.AllowReads(entity)).Returns(expectedResult); + + //Act + var result = _sut.AllowRead(entity); + + //Assert + Assert.Equal(expectedResult, result); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void AllowWriteAccess_Returns_Response_From_Context(bool expectedResult) + { + //Arrange + var entity = Mock.Of(); + _authContext.Setup(x => x.AllowModify(entity)).Returns(expectedResult); + + //Act + var result = _sut.AllowModify(entity); + + //Assert + Assert.Equal(expectedResult, result); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void AllowEntityVisibilityControl_Returns_Response_From_Context(bool expectedResult) + { + //Arrange + var entity = Mock.Of(); + _authContext.Setup(x => x.AllowEntityVisibilityControl(entity)).Returns(expectedResult); + + //Act + var result = _sut.AllowEntityVisibilityControl(entity); + + //Assert + Assert.Equal(expectedResult, result); + } + } +} diff --git a/Tests.Unit.Presentation.Web/Authorization/LegacyAuthorizationStrategyTest.cs b/Tests.Unit.Presentation.Web/Authorization/LegacyAuthorizationStrategyTest.cs new file mode 100644 index 0000000000..f807a085e6 --- /dev/null +++ b/Tests.Unit.Presentation.Web/Authorization/LegacyAuthorizationStrategyTest.cs @@ -0,0 +1,112 @@ +using Core.ApplicationServices; +using Core.DomainModel; +using Core.DomainServices.Authorization; +using Moq; +using Presentation.Web.Infrastructure.Authorization.Controller; +using Tests.Unit.Presentation.Web.Helpers; +using Xunit; + +namespace Tests.Unit.Presentation.Web.Authorization +{ + public class LegacyAuthorizationStrategyTest : WithAutoFixture + { + private readonly int _userId; + private readonly LegacyAuthorizationStrategy _sut; + private readonly Mock _authenticationService; + + public LegacyAuthorizationStrategyTest() + { + _userId = A(); + _authenticationService = new Mock(); + _sut = new LegacyAuthorizationStrategy(_authenticationService.Object, () => _userId); + } + + [Theory] + [InlineData(true, false, CrossOrganizationDataReadAccessLevel.All)] + [InlineData(false, true, CrossOrganizationDataReadAccessLevel.Public)] + [InlineData(false, false, CrossOrganizationDataReadAccessLevel.None)] + public void GetCrossOrganizationReadAccess_Returns(bool isGlobalAdmin, bool hasReadAccessOutsideOfContext, CrossOrganizationDataReadAccessLevel expectedResult) + { + //Arrange + _authenticationService.Setup(x => x.IsGlobalAdmin(_userId)).Returns(isGlobalAdmin); + ExpectHasReadAccessOutsideOfContextReturns(hasReadAccessOutsideOfContext); + + //Act + var readAccess = _sut.GetCrossOrganizationReadAccess(); + + //Assert + Assert.Equal(expectedResult, readAccess); + } + + [Theory] + [InlineData(true, false, OrganizationDataReadAccessLevel.All)] + [InlineData(false, true, OrganizationDataReadAccessLevel.All)] + [InlineData(false, false, OrganizationDataReadAccessLevel.None)] + public void GetOrganizationReadAccessLevel_Returns(bool sameOrg, bool readAccessOutsideOfContext, OrganizationDataReadAccessLevel expectedAccessLevel) + { + //Arrange + var organizationId = A(); + _authenticationService.Setup(x => x.GetCurrentOrganizationId(_userId)).Returns(sameOrg ? organizationId : A()); + ExpectHasReadAccessOutsideOfContextReturns(readAccessOutsideOfContext); + + //Act + var readAccess = _sut.GetOrganizationReadAccessLevel(organizationId); + + //Assert + Assert.Equal(expectedAccessLevel, readAccess); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void AllowReadAccess_Returns_Result_From_AuthenticationService(bool expectedResult) + { + //Arrange + var entity = Mock.Of(); + _authenticationService.Setup(x => x.HasReadAccess(_userId, entity)).Returns(expectedResult); + + //Act + var result = _sut.AllowRead(entity); + + //Assert + Assert.Equal(expectedResult, result); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void AllowWriteAccess_Returns_Result_From_AuthenticationService(bool expectedResult) + { + //Arrange + var entity = Mock.Of(); + _authenticationService.Setup(x => x.HasWriteAccess(_userId, entity)).Returns(expectedResult); + + //Act + var result = _sut.AllowModify(entity); + + //Assert + Assert.Equal(expectedResult, result); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void AllowEntityVisibilityControl_Returns_Result_From_AuthenticationService_FeatureCheck(bool expectedResult) + { + //Arrange + var entity = Mock.Of(); + _authenticationService.Setup(x => x.CanExecute(_userId, Feature.CanSetAccessModifierToPublic)).Returns(expectedResult); + + //Act + var result = _sut.AllowEntityVisibilityControl(entity); + + //Assert + Assert.Equal(expectedResult, result); + } + + private void ExpectHasReadAccessOutsideOfContextReturns(bool hasReadAccessOutsideOfContext) + { + _authenticationService.Setup(x => x.HasReadAccessOutsideContext(_userId)).Returns(hasReadAccessOutsideOfContext); + } + } +} diff --git a/Tests.Unit.Presentation.Web/Authorization/OrganizationAuthorizationContextTest.cs b/Tests.Unit.Presentation.Web/Authorization/OrganizationAuthorizationContextTest.cs new file mode 100644 index 0000000000..a2483ad217 --- /dev/null +++ b/Tests.Unit.Presentation.Web/Authorization/OrganizationAuthorizationContextTest.cs @@ -0,0 +1,442 @@ +using Core.ApplicationServices.Authorization; +using Core.DomainModel; +using Core.DomainModel.ItContract; +using Core.DomainModel.ItProject; +using Core.DomainModel.ItSystem; +using Core.DomainModel.ItSystemUsage; +using Core.DomainModel.Organization; +using Core.DomainServices.Authorization; +using Moq; +using Tests.Unit.Presentation.Web.Helpers; +using Xunit; + +namespace Tests.Unit.Presentation.Web.Authorization +{ + public class OrganizationAuthorizationContextTest : WithAutoFixture + { + private readonly Mock _userContextMock; + private readonly OrganizationAuthorizationContext _sut; + + public OrganizationAuthorizationContextTest() + { + _userContextMock = new Mock(); + _sut = new OrganizationAuthorizationContext(_userContextMock.Object); + } + + [Theory] + [InlineData(true, OrganizationCategory.Other, CrossOrganizationDataReadAccessLevel.All)] + [InlineData(false, OrganizationCategory.Municipality, CrossOrganizationDataReadAccessLevel.Public)] + [InlineData(false, OrganizationCategory.Other, CrossOrganizationDataReadAccessLevel.None)] + public void GetCrossOrganizationReadAccess_Returns_Based_On_Role_And_Organization_Type(bool isGlobalAdmin, OrganizationCategory organizationCategory, CrossOrganizationDataReadAccessLevel expectedResult) + { + //Arrange + ExpectHasRoleReturns(OrganizationRole.GlobalAdmin, isGlobalAdmin); + ExpectIsActiveInOrganizationOfTypeReturns(OrganizationCategory.Municipality, organizationCategory == OrganizationCategory.Municipality); + + //Act + var result = _sut.GetCrossOrganizationReadAccess(); + + //Assert + Assert.Equal(expectedResult, result); + } + + [Theory] + [InlineData(true, false, false, OrganizationDataReadAccessLevel.All)] + [InlineData(false, true, false, OrganizationDataReadAccessLevel.All)] + [InlineData(false, false, true, OrganizationDataReadAccessLevel.Public)] + [InlineData(false, false, false, OrganizationDataReadAccessLevel.None)] + public void GetOrganizationReadAccessLevel_Returns(bool isGlobalAdmin, bool isActiveInOrganization, bool isMunicipality, OrganizationDataReadAccessLevel expectedResult) + { + //Arrange + var targetOrganization = A(); + ExpectHasRoleReturns(OrganizationRole.GlobalAdmin, isGlobalAdmin); + ExpectIsActiveInOrganizationReturns(targetOrganization, isActiveInOrganization); + ExpectIsActiveInOrganizationOfTypeReturns(OrganizationCategory.Municipality, isMunicipality); + + //Act + var hasAccess = _sut.GetOrganizationReadAccessLevel(targetOrganization); + + //Assert + Assert.Equal(expectedResult, hasAccess); + } + + [Theory] + [InlineData(true, false, false, false, AccessModifier.Local, true)] + [InlineData(false, true, false, false, AccessModifier.Local, true)] + [InlineData(false, false, true, false, AccessModifier.Local, true)] + [InlineData(false, false, false, true, AccessModifier.Public, true)] + [InlineData(false, false, false, true, AccessModifier.Local, false)] + [InlineData(false, false, false, false, AccessModifier.Public, false)] + public void AllowReads_For_Context_Dependent_Object_Returns(bool isGlobalAdmin, bool inputIsActiveUser, bool isInSameOrg, bool isUserActiveInMunicipality, AccessModifier accessModifier, bool expectedResult) + { + //Arrange + var userId = A(); + var entity = inputIsActiveUser ? CreateUserEntity(userId) : CreateTestItSystem(accessModifier); + + ExpectHasRoleReturns(OrganizationRole.GlobalAdmin, isGlobalAdmin); + ExpectGetUserIdReturns(userId); + ExpectIsActiveInSameOrganizationAsReturns(entity, isInSameOrg); + ExpectIsActiveInOrganizationOfTypeReturns(OrganizationCategory.Municipality, isUserActiveInMunicipality); + + //Act + var result = _sut.AllowReads(entity); + + //Assert + Assert.Equal(expectedResult, result); + } + + [Theory] + [InlineData(true, false, true)] + [InlineData(false, true, true)] + [InlineData(false, false, false)] + public void AllowReads_For_Context_Independent_Object_Returns(bool isGlobalAdmin, bool inputIsActiveUser, bool expectedResult) + { + //Arrange + var activeUserId = A(); + var inputEntity = inputIsActiveUser ? CreateUserEntity(activeUserId) : Mock.Of(); + + ExpectHasRoleReturns(OrganizationRole.GlobalAdmin, isGlobalAdmin); + ExpectGetUserIdReturns(activeUserId); + + //Act + var result = _sut.AllowReads(inputEntity); + + //Assert + Assert.Equal(expectedResult, result); + } + + [Theory] + //Checks not bound to context condition + [InlineData(true, false, false, false, false, false, false, false, true)] + [InlineData(false, true, false, false, false, false, false, false, true)] + [InlineData(false, false, true, true, false, false, false, false, true)] + + //Same organization - positive matches + [InlineData(false, false, false, true, true, false, false, false, true)] + [InlineData(false, false, false, true, false, true, false, false, true)] + [InlineData(false, false, false, true, false, false, false, true, true)] + + //Same organization - negative matches + [InlineData(false, false, false, true, false, false, false, false, false)] + [InlineData(false, false, false, true, false, false, true, true, false)] + + //Different organization for context bound object + [InlineData(false, false, false, false, true, false, false, false, false)] + public void AllowUpdates_For_Context_Dependent_Object_Returns( + bool isGlobalAdmin, + bool inputIsActiveUser, + bool hasAssignedWriteAccess, + bool isInSameOrganization, + bool isLocalAdmin, + bool hasModuleLevelAccess, + bool inputIsAUser, + bool hasOwnership, + bool expectedResult) + { + //Arrange + var userId = A(); + var inputEntity = inputIsActiveUser || inputIsAUser ? CreateUserEntity(inputIsActiveUser ? userId : A()) : CreateTestItSystem(AccessModifier.Public); + + ExpectHasRoleReturns(OrganizationRole.GlobalAdmin, isGlobalAdmin); + ExpectGetUserIdReturns(userId); + ExpectHasAssignedWriteAccessReturns(inputEntity, hasAssignedWriteAccess); + ExpectIsActiveInSameOrganizationAsReturns(inputEntity, isInSameOrganization); + ExpectHasRoleReturns(OrganizationRole.LocalAdmin, isLocalAdmin); + ExpectHasModuleLevelAccessReturns(inputEntity, hasModuleLevelAccess); + ExpectHasOwnershipReturns(inputEntity, hasOwnership); + + //Act + var allowUpdates = _sut.AllowModify(inputEntity); + + //Assert + Assert.Equal(expectedResult, allowUpdates); + } + + [Theory] + [InlineData(true, false, false, false, true)] + [InlineData(false, true, false, false, true)] + [InlineData(false, false, true, false, true)] + [InlineData(false, false, false, true, true)] + [InlineData(false, false, false, false, false)] + public void AllowUpdates_For_Context_Independent_Object_Returns( + bool isGlobalAdmin, + bool inputIsActiveUser, + bool hasModuleLevelAccess, + bool hasOwnership, + bool expectedResult) + { + //Arrange + var userId = A(); + var inputEntity = inputIsActiveUser ? CreateUserEntity(userId) : Mock.Of(); + + ExpectHasRoleReturns(OrganizationRole.GlobalAdmin, isGlobalAdmin); + ExpectGetUserIdReturns(userId); + ExpectHasModuleLevelAccessReturns(inputEntity, hasModuleLevelAccess); + ExpectHasOwnershipReturns(inputEntity, hasOwnership); + + //Act + var allowUpdates = _sut.AllowModify(inputEntity); + + //Assert + Assert.Equal(expectedResult, allowUpdates); + } + + [Theory] + [InlineData(true, true, true)] + [InlineData(false, true, false)] + [InlineData(true, false, false)] + public void AllowEntityVisibilityControl_Returns_True_If_HasWriteAccess_And_Is_AllowedToModifyVisibility(bool isGlobalAdmin, bool isAllowedToChangeVisibility, bool expectedResult) + { + //Arrange + var userId = A(); + var inputEntity = Mock.Of(); + + ExpectHasRoleReturns(OrganizationRole.GlobalAdmin, isGlobalAdmin); + ExpectGetUserIdReturns(userId); + ExpectCanChangeVisibilityOfReturns(isAllowedToChangeVisibility, inputEntity); + + //Act + var allowUpdates = _sut.AllowEntityVisibilityControl(inputEntity); + + //Assert + Assert.Equal(expectedResult, allowUpdates); + } + + [Theory] + [InlineData(true, false, true)] + [InlineData(true, true, false)] + [InlineData(false, false, false)] + public void Allow_Create_ItSystem_Returns(bool isGlobalAdmin, bool isReadOnly, bool expectedResult) + { + Allow_Create_Returns(isGlobalAdmin, isReadOnly, expectedResult); + } + + [Theory] + [InlineData(false, true)] + [InlineData(true, false)] + public void Allow_Create_ItContract_Returns(bool isReadOnly, bool expectedResult) + { + Allow_Create_Returns(false, isReadOnly, expectedResult); + } + + [Theory] + [InlineData(false, true)] + [InlineData(true, false)] + public void Allow_Create_ItSystemUsage_Returns(bool isReadOnly, bool expectedResult) + { + Allow_Create_Returns(false, isReadOnly, expectedResult); + } + + [Theory] + [InlineData(false, true)] + [InlineData(true, false)] + public void Allow_Create_ItProject_Returns(bool isReadOnly, bool expectedResult) + { + Allow_Create_Returns(false, isReadOnly, expectedResult); + } + + [Theory] + [InlineData(false, true)] + [InlineData(true, false)] + public void Allow_Create_ItInterface_Returns(bool isReadOnly, bool expectedResult) + { + Allow_Create_Returns(false, isReadOnly, expectedResult); + } + + [Theory] + [InlineData(false, true)] + [InlineData(true, false)] + public void Allow_Create_Organization_Returns(bool isReadOnly, bool expectedResult) + { + Allow_Create_Returns(false, isReadOnly, expectedResult); + } + + [Theory] + [InlineData(false, true)] + [InlineData(true, false)] + public void Allow_Create_User_Returns(bool isReadOnly, bool expectedResult) + { + Allow_Create_Returns(false, isReadOnly, expectedResult); + } + + [Theory] + //Checks not bound to context condition + [InlineData(true, false, false, false, false, false, false, false, true)] + [InlineData(false, true, false, false, false, false, false, false, true)] + [InlineData(false, false, true, true, false, false, false, false, true)] + + //Same organization - positive matches + [InlineData(false, false, false, true, true, false, false, false, true)] + [InlineData(false, false, false, true, false, true, false, false, true)] + [InlineData(false, false, false, true, false, false, false, true, true)] + + //Same organization - negative matches + [InlineData(false, false, false, true, false, false, false, false, false)] + [InlineData(false, false, false, true, false, false, true, true, false)] + + //Different organization for context bound object + [InlineData(false, false, false, false, true, false, false, false, false)] + public void AllowDelete_For_Context_Dependent_Object_Returns( + bool isGlobalAdmin, + bool inputIsActiveUser, + bool hasAssignedWriteAccess, + bool isInSameOrganization, + bool isLocalAdmin, + bool hasModuleLevelAccess, + bool inputIsAUser, + bool hasOwnership, + bool expectedResult) + { + //Arrange + var userId = A(); + var inputEntity = inputIsActiveUser || inputIsAUser ? CreateUserEntity(inputIsActiveUser ? userId : A()) : CreateItProject(AccessModifier.Public); + + ExpectHasRoleReturns(OrganizationRole.GlobalAdmin, isGlobalAdmin); + ExpectGetUserIdReturns(userId); + ExpectHasAssignedWriteAccessReturns(inputEntity, hasAssignedWriteAccess); + ExpectIsActiveInSameOrganizationAsReturns(inputEntity, isInSameOrganization); + ExpectHasRoleReturns(OrganizationRole.LocalAdmin, isLocalAdmin); + ExpectHasModuleLevelAccessReturns(inputEntity, hasModuleLevelAccess); + ExpectHasOwnershipReturns(inputEntity, hasOwnership); + + //Act + var allowUpdates = _sut.AllowDelete(inputEntity); + + //Assert + Assert.Equal(expectedResult, allowUpdates); + } + + [Theory] + [InlineData(true, false, false, false, true)] + [InlineData(false, true, false, false, true)] + [InlineData(false, false, true, false, true)] + [InlineData(false, false, false, true, true)] + [InlineData(false, false, false, false, false)] + public void AllowDelete_For_Context_Independent_Object_Returns( + bool isGlobalAdmin, + bool inputIsActiveUser, + bool hasModuleLevelAccess, + bool hasOwnership, + bool expectedResult) + { + //Arrange + var userId = A(); + var inputEntity = inputIsActiveUser ? CreateUserEntity(userId) : Mock.Of(); + + ExpectHasRoleReturns(OrganizationRole.GlobalAdmin, isGlobalAdmin); + ExpectGetUserIdReturns(userId); + ExpectHasModuleLevelAccessReturns(inputEntity, hasModuleLevelAccess); + ExpectHasOwnershipReturns(inputEntity, hasOwnership); + + //Act + var allowUpdates = _sut.AllowDelete(inputEntity); + + //Assert + Assert.Equal(expectedResult, allowUpdates); + } + + [Theory] + [InlineData(false, true, false, false, true)] + [InlineData(false, false, true, true, true)] + [InlineData(true, true, false, false, false)] + [InlineData(true, false, true, true, false)] + [InlineData(false, false, false, true, false)] + [InlineData(false, false, true, false, false)] + public void AllowDelete_For_ItSystem_Object_Returns( + bool isReadOnly, + bool isGlobalAdmin, + bool isInSameOrganization, + bool isLocalAdmin, + bool expectedResult) + { + //Arrange + var userId = A(); + var inputEntity = CreateTestItSystem(AccessModifier.Public); + + ExpectHasRoleReturns(OrganizationRole.GlobalAdmin, isGlobalAdmin); + ExpectHasRoleReturns(OrganizationRole.ReadOnly, isReadOnly); + ExpectGetUserIdReturns(userId); + ExpectIsActiveInSameOrganizationAsReturns(inputEntity, isInSameOrganization); + ExpectHasRoleReturns(OrganizationRole.LocalAdmin, isLocalAdmin); + + //Act + var allowUpdates = _sut.AllowDelete(inputEntity); + + //Assert + Assert.Equal(expectedResult, allowUpdates); + } + + private void Allow_Create_Returns(bool isGlobalAdmin, bool isReadOnly, bool expectedResult) + { + //Arrange + ExpectHasRoleReturns(OrganizationRole.GlobalAdmin, isGlobalAdmin); + ExpectHasRoleReturns(OrganizationRole.ReadOnly, isReadOnly); + + //Act + var result = _sut.AllowCreate(); + + //Assert + Assert.Equal(expectedResult, result); + } + + private void ExpectCanChangeVisibilityOfReturns(bool isAllowedToChangeVisibility, IEntity inputEntity) + { + _userContextMock.Setup(x => x.CanChangeVisibilityOf(inputEntity)).Returns(isAllowedToChangeVisibility); + } + + private void ExpectHasOwnershipReturns(IEntity inputEntity, bool value) + { + _userContextMock.Setup(x => x.HasOwnership(inputEntity)).Returns(value); + } + + private void ExpectHasModuleLevelAccessReturns(IEntity inputEntity, bool hasModuleLevelAccess) + { + _userContextMock.Setup(x => x.HasModuleLevelAccessTo(inputEntity)).Returns(hasModuleLevelAccess); + } + + private void ExpectHasAssignedWriteAccessReturns(IEntity inputEntity, bool value) + { + _userContextMock.Setup(x => x.HasAssignedWriteAccess(inputEntity)).Returns(value); + } + + private static ItSystem CreateTestItSystem(AccessModifier accessModifier) + { + return new ItSystem { AccessModifier = accessModifier }; + } + + private static ItProject CreateItProject(AccessModifier accessModifier) + { + return new ItProject { AccessModifier = accessModifier }; + } + + private void ExpectIsActiveInSameOrganizationAsReturns(IEntity entity, bool value) + { + _userContextMock.Setup(x => x.IsActiveInSameOrganizationAs(entity)).Returns(value); + } + + private void ExpectGetUserIdReturns(int userId) + { + _userContextMock.Setup(x => x.UserId).Returns(userId); + } + + private void ExpectIsActiveInOrganizationOfTypeReturns(OrganizationCategory organizationCategory, bool value) + { + _userContextMock.Setup(x => x.IsActiveInOrganizationOfType(organizationCategory)).Returns(value); + } + + private void ExpectIsActiveInOrganizationReturns(int targetOrganization, bool value) + { + _userContextMock.Setup(x => x.IsActiveInOrganization(targetOrganization)).Returns(value); + } + + private void ExpectHasRoleReturns(OrganizationRole role, bool value) + { + _userContextMock.Setup(x => x.HasRole(role)).Returns(value); + } + + private static IEntity CreateUserEntity(int id) + { + return (IEntity)new User() { Id = id }; + } + } +} diff --git a/Tests.Unit.Presentation.Web/Authorization/OrganizationalUserContextTest.cs b/Tests.Unit.Presentation.Web/Authorization/OrganizationalUserContextTest.cs new file mode 100644 index 0000000000..6a2e6ea4e0 --- /dev/null +++ b/Tests.Unit.Presentation.Web/Authorization/OrganizationalUserContextTest.cs @@ -0,0 +1,325 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Core.ApplicationServices; +using Core.ApplicationServices.Authorization; +using Core.DomainModel; +using Core.DomainModel.ItContract; +using Core.DomainModel.ItProject; +using Core.DomainModel.ItSystem; +using Core.DomainModel.Organization; +using Core.DomainModel.Reports; +using Moq; +using Presentation.Web.Infrastructure.Authorization; +using Tests.Unit.Presentation.Web.Helpers; +using Xunit; + +namespace Tests.Unit.Presentation.Web.Authorization +{ + public class OrganizationalUserContextTest : WithAutoFixture + { + [Theory] + [MemberData(nameof(GetModuleAccessTestInputs))] + public void HasModuleLevelAccessTo_Returns_Correct_Result(IEntity entity, IReadOnlyList supportedFeatures, bool expectedResult) + { + //Arrange + var sut = new OrganizationalUserContext(supportedFeatures, Many(), new User(), A()); + + //Act + var result = sut.HasModuleLevelAccessTo(entity); + + //Assert + Assert.Equal(expectedResult, result); + } + + [Fact] + public void IsActiveInOrganization_Returns_True_If_OrgId_Matches_ActiveOrgId() + { + //Arrange + var activeOrganizationId = A(); + var sut = new OrganizationalUserContext(Many(), Many(), new User(), activeOrganizationId); + + //Act + var result = sut.IsActiveInOrganization(activeOrganizationId); + + //Assert + Assert.True(result); + } + + [Fact] + public void IsActiveInOrganization_Returns_False_If_OrgId_Differs_From_ActiveOrgId() + { + //Arrange + var activeOrganizationId = A(); + var otherOrgId = activeOrganizationId + 1; + var sut = new OrganizationalUserContext(Many(), Many(), new User(), activeOrganizationId); + + //Act + var result = sut.IsActiveInOrganization(otherOrgId); + + //Assert + Assert.False(result); + } + + public interface IEntityWithContextAware : IEntity, IContextAware { } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void IsActiveInSameOrganizationAs_Returns_Result_Based_On_Active_Context_Query(bool contextResult) + { + //Arrange + var activeOrganizationId = A(); + var sut = new OrganizationalUserContext(Many(), Many(), new User(), activeOrganizationId); + var target = new Mock(); + target.Setup(x => x.IsInContext(activeOrganizationId)).Returns(contextResult); + + //Act + var result = sut.IsActiveInSameOrganizationAs(target.Object); + + //Assert + target.Verify(); + Assert.Equal(contextResult, result); + } + + public interface IEntityWithOrganization : IEntity, IHasOrganization { } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void IsActiveInSameOrganizationAs_Returns_Result_Based_On_Same_Organization_Som_OrganizationId_Property(bool sameOrg) + { + //Arrange + var activeOrganizationId = A(); + var sut = new OrganizationalUserContext(Many(), Many(), new User(), activeOrganizationId); + var target = new Mock(); + target.Setup(x => x.OrganizationId).Returns(sameOrg ? activeOrganizationId : A()); + + //Act + var result = sut.IsActiveInSameOrganizationAs(target.Object); + + //Assert + target.Verify(); + Assert.Equal(sameOrg, result); + } + + [Theory] + [MemberData(nameof(GetRoles))] + public void HasRole_Returns_True_For_Supported_Roles_And_False_For_Unsupported(OrganizationRole unsupportedRole) + { + //Arrange + var allRoles = Enum.GetValues(typeof(OrganizationRole)).Cast().ToList(); + var supportedRoles = allRoles.Except(new[] { unsupportedRole }).ToList(); + var sut = new OrganizationalUserContext(Many(), supportedRoles, new User(), A()); + + //Act + var results = allRoles.Select(role => new + { + Role = role, + Result = sut.HasRole(role), + ExpectedResult = role != unsupportedRole + }).ToList(); + + //Assert + foreach (var result in results) + { + Assert.Equal(result.ExpectedResult, result.Result); + } + } + + [Fact] + public void UserId_Returns_Provided_Users_Id() + { + var user = new User { Id = A() }; + + var sut = new OrganizationalUserContext(Many(), Many(), user, A()); + + Assert.Equal(sut.UserId, user.Id); + } + + [Fact] + public void ActiveOrganizationId_Returns_Provided_OrganizationId() + { + var organizationId = A(); + + var sut = new OrganizationalUserContext(Many(), Many(), new User(), organizationId); + + Assert.Equal(organizationId, sut.ActiveOrganizationId); + } + + [Theory] + [InlineData(OrganizationCategory.Municipality, OrganizationCategory.Municipality, true)] + [InlineData(OrganizationCategory.Municipality, OrganizationCategory.Other, false)] + [InlineData(OrganizationCategory.Other, OrganizationCategory.Other, true)] + [InlineData(OrganizationCategory.Other, OrganizationCategory.Municipality, false)] + public void IsActiveInOrganizationOfType_Returns(OrganizationCategory inputCategory, OrganizationCategory activeCategory, bool expectedResult) + { + //Arrange + var user = new User + { + DefaultOrganization = new Organization + { + Type = new OrganizationType + { + Category = activeCategory + } + } + }; + var sut = new OrganizationalUserContext(Many(), Many(), user, A()); + + //Act + var result = sut.IsActiveInOrganizationOfType(inputCategory); + + //Assert + Assert.Equal(expectedResult, result); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void HasAssignedWriteAccess_Delegates_Question_To_Provided_Entity(bool hasAccess) + { + //Arrange + var user = new User(); + var sut = new OrganizationalUserContext(Many(), Many(), user, A()); + var entity = Mock.Of(x => x.HasUserWriteAccess(user) == hasAccess); + + //Act + var result = sut.HasAssignedWriteAccess(entity); + + //Assert + Assert.Equal(hasAccess, result); + } + + [Theory] + [InlineData(1, 1, true)] + [InlineData(1, 2, false)] + [InlineData(2, 1, false)] + public void HasOwnership_Returns_Based_On_OwnerId(int entityOwnerId, int userId, bool expectedResult) + { + //Arrange + var user = new User() { Id = userId }; + var sut = new OrganizationalUserContext(Many(), Many(), user, A()); + var entity = Mock.Of(x => x.ObjectOwnerId == entityOwnerId); + + //Act + var result = sut.HasOwnership(entity); + + //Assert + Assert.Equal(expectedResult, result); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void CanChangeVisibilityOf_ContractModuleEntity_Returns(bool hasAccess) + { + //Arrange + var features = hasAccess + ? GetFeatureOptions() + : GetFeatureOptions(Feature.CanSetContractElementsAccessModifierToPublic); + + var sut = new OrganizationalUserContext(features, Many(), new User(), A()); + + //Act + var result = sut.CanChangeVisibilityOf(new EconomyStream()); + + //Assert + Assert.Equal(hasAccess, result); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void CanChangeVisibilityOf_OrganizationModuleEntity_Returns(bool hasAccess) + { + //Arrange + var features = hasAccess + ? GetFeatureOptions() + : GetFeatureOptions(Feature.CanSetOrganizationAccessModifierToPublic); + + var sut = new OrganizationalUserContext(features, Many(), new User(), A()); + + //Act + var result = sut.CanChangeVisibilityOf(new Organization()); + + //Assert + Assert.Equal(hasAccess, result); + } + + [Theory, MemberData(nameof(GetNonSpecificVisibilityChangeTypeTestInputs))] + public void CanChangeVisibilityOf_OrganizationModuleEntity_Returns(IEntity inputType, bool hasAccess) + { + //Arrange + var features = hasAccess + ? GetFeatureOptions() + : GetFeatureOptions(Feature.CanSetAccessModifierToPublic); + + var sut = new OrganizationalUserContext(features, Many(), new User(), A()); + + //Act + var result = sut.CanChangeVisibilityOf(inputType); + + //Assert + Assert.Equal(hasAccess, result); + } + + #region helpers + + public static IEnumerable GetNonSpecificVisibilityChangeTypeTestInputs() + { + yield return new object[] { new ItSystem(), true }; + yield return new object[] { new ItSystem(), false }; + + yield return new object[] { new ItProject(), true }; + yield return new object[] { new ItProject(), false }; + + yield return new object[] { new Report(), true }; + yield return new object[] { new Report(), false }; + } + + + public static IEnumerable GetRoles() + { + return Enum.GetValues(typeof(OrganizationRole)).Cast().Select(x => new[] { x }); + } + + public static IEnumerable GetModuleAccessTestInputs() + { + //Systems + yield return new object[] { Mock.Of(), GetFeatureOptions(), true }; + yield return new object[] { Mock.Of(), GetFeatureOptions(Feature.CanModifySystems), false }; + + //Contracts + yield return new object[] { Mock.Of(), GetFeatureOptions(), true }; + yield return new object[] { Mock.Of(), GetFeatureOptions(Feature.CanModifyContracts), false }; + + //Organizations + yield return new object[] { Mock.Of(), GetFeatureOptions(), true }; + yield return new object[] { Mock.Of(), GetFeatureOptions(Feature.CanModifyOrganizations), false }; + + //Projects + yield return new object[] { Mock.Of(), GetFeatureOptions(), true }; + yield return new object[] { Mock.Of(), GetFeatureOptions(Feature.CanModifyProjects), false }; + + //Users + yield return new object[] { Mock.Of(), GetFeatureOptions(), true }; + yield return new object[] { Mock.Of(), GetFeatureOptions(Feature.CanModifyUsers), false }; + + //Users + yield return new object[] { Mock.Of(), GetFeatureOptions(), true }; + yield return new object[] { Mock.Of(), GetFeatureOptions(Feature.CanModifyReports), false }; + } + + private static IReadOnlyList GetFeatureOptions(params Feature[] unsupportedFeatures) + { + return + Enum + .GetValues(typeof(Feature)) + .Cast() + .Except(unsupportedFeatures) + .ToList(); + } + #endregion helpers + } +} diff --git a/Tests.Unit.Presentation.Web/Context/OwinAuthenticationContextFactoryTest.cs b/Tests.Unit.Presentation.Web/Context/OwinAuthenticationContextFactoryTest.cs new file mode 100644 index 0000000000..34a05ea186 --- /dev/null +++ b/Tests.Unit.Presentation.Web/Context/OwinAuthenticationContextFactoryTest.cs @@ -0,0 +1,151 @@ +using System.Collections.Generic; +using System.Security.Claims; +using Core.ApplicationServices.Authentication; +using Core.DomainModel; +using Core.DomainServices; +using Microsoft.Owin; +using Microsoft.Owin.Security; +using Moq; +using Presentation.Web.Infrastructure.Factories.Authentication; +using Presentation.Web.Infrastructure.Model.Authentication; +using Serilog; +using Tests.Unit.Presentation.Web.Helpers; +using Xunit; + +namespace Tests.Unit.Presentation.Web.Context +{ + public class OwinAuthenticationContextFactoryTest : WithAutoFixture + { + private readonly int _validUserId; + private const string TokenAuth = "JWT"; + + public OwinAuthenticationContextFactoryTest() + { + _validUserId = A(); + } + + [Fact] + public void Unauthenticated_User_Should_Return_AuthenticationContext_With_Anonymous_AuthenticationMethod() + { + //Arrange + var authenticationContextFactory = new OwinAuthenticationContextFactory(Mock.Of(), MakeMockContext(authType: null, defaultOrg: "invalid", userId: "1", isAuthenticated: "false"), Mock.Of()); + + //Act + var authContext = authenticationContextFactory.Create(); + + //Assert + Assert.Equal(AuthenticationMethod.Anonymous, authContext.Method); + } + + [Theory] + [InlineData(AuthenticationMethod.KitosToken, "JWT", 1234)] + [InlineData(AuthenticationMethod.Forms, "Forms", 1337)] + [InlineData(AuthenticationMethod.Anonymous, "None", null)] + public void Authenticated_User_Should_Return_AuthenticationContext_With_AuthenticationMethod(AuthenticationMethod authMethod, string authType, int? defaultOrg) + { + //Arrange + var owinContext = MakeMockContext(authType: authType, defaultOrg: defaultOrg?.ToString() ?? A(), userId: _validUserId.ToString(), isAuthenticated: "true"); + var userRepository = MakeMockUserRepository(false, _validUserId, defaultOrg); + var authenticationContextFactory = new OwinAuthenticationContextFactory(Mock.Of(), owinContext, userRepository); + + //Act + var authContext = authenticationContextFactory.Create(); + + //Assert + Assert.Equal(authMethod, authContext.Method); + Assert.Equal(defaultOrg, authContext.ActiveOrganizationId); + Assert.Equal(_validUserId, authContext.UserId); + } + + [Fact] + public void Invalid_Organization_Claim_Value_Returns_Null() + { + //Arrange + var owinContext = MakeMockContext(authType: TokenAuth, defaultOrg: "invalid", userId: _validUserId.ToString(), isAuthenticated: "true"); + var userRepository = MakeMockUserRepository(false, _validUserId); + var authenticationContextFactory = new OwinAuthenticationContextFactory(Mock.Of(), owinContext, userRepository); + + //Act + var authContext = authenticationContextFactory.Create(); + + //Assert + Assert.Null(authContext.ActiveOrganizationId); + Assert.Equal(AuthenticationMethod.KitosToken, authContext.Method); + } + + [Fact] + public void Invalid_UserId_Returns_Null() + { + //Arrange + var owinContext = MakeMockContext(authType: TokenAuth, defaultOrg: "1", userId: "invalid", isAuthenticated: "true"); + var userRepository = MakeMockUserRepository(false, _validUserId); + var authenticationContextFactory = new OwinAuthenticationContextFactory(Mock.Of(), owinContext, userRepository); + + //Act + var authContext = authenticationContextFactory.Create(); + + //Assert + Assert.Null(authContext.UserId); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Authenticated_User_Can_Have_Api_Access(bool apiAccess) + { + //Arrange + var owinContext = MakeMockContext(authType: TokenAuth, defaultOrg: "1", userId: _validUserId.ToString(), isAuthenticated: "true"); + var userRepository = MakeMockUserRepository(apiAccess, _validUserId); + var authenticationContextFactory = new OwinAuthenticationContextFactory(Mock.Of(), owinContext, userRepository); + + //Act + var authContext = authenticationContextFactory.Create(); + + //Assert + Assert.Equal(_validUserId, authContext.UserId); + Assert.Equal(apiAccess, authContext.HasApiAccess); + } + + [Fact] + public void Unauthenticated_User_Can_Not_Have_Api_Access() + { + //Arrange + var authenticationContextFactory = new OwinAuthenticationContextFactory(Mock.Of(), MakeMockContext(authType: null, defaultOrg: "invalid", userId: "1", isAuthenticated: "false"), Mock.Of()); + + //Act + var authContext = authenticationContextFactory.Create(); + + //Assert + Assert.Equal(false, authContext.HasApiAccess); + } + + private static IUserRepository MakeMockUserRepository(bool apiAccess, int userId, int? defaultOrgId = null) + { + var user = new User(); + user.HasApiAccess = apiAccess; + user.Id = userId; + user.DefaultOrganizationId = defaultOrgId; + + var userRepo = new Mock(); + userRepo.Setup(_ => _.GetById(userId)).Returns(user); + return userRepo.Object; + } + + private IOwinContext MakeMockContext(string authType, string defaultOrg, string userId, string isAuthenticated) + { + var claims = new List + { + new Claim(BearerTokenConfig.DefaultOrganizationClaimName, defaultOrg), + new Claim(ClaimTypes.Name, userId), + new Claim(ClaimTypes.Authentication, isAuthenticated, ClaimValueTypes.Boolean) + }; + var identity = new ClaimsIdentity(claims, authType); + var user = new ClaimsPrincipal(identity); + var authManager = new Mock(); + var context = new Mock(); + context.SetupGet(c => c.Authentication).Returns(authManager.Object); + context.SetupGet(p => p.Authentication.User).Returns(user); + return context.Object; + } + } +} diff --git a/Tests.Unit.Presentation.Web/Controllers/ItSystemControllerTest.cs b/Tests.Unit.Presentation.Web/Controllers/ItSystemControllerTest.cs new file mode 100644 index 0000000000..5ec4012d39 --- /dev/null +++ b/Tests.Unit.Presentation.Web/Controllers/ItSystemControllerTest.cs @@ -0,0 +1,124 @@ +using System.Net; +using Core.ApplicationServices; +using Core.ApplicationServices.Authorization; +using Core.DomainModel.ItSystem; +using Core.DomainModel.Organization; +using Core.DomainServices; +using Core.DomainServices.Authorization; +using Moq; +using Presentation.Web.Controllers.API; +using Presentation.Web.Models; +using Tests.Unit.Presentation.Web.Helpers; +using Xunit; + +namespace Tests.Unit.Presentation.Web.Controllers +{ + public class ItSystemControllerTest : KitosRestControllerApiTestWithAutofixture + { + private readonly Mock _authorizationContext; + private readonly Mock> _systemRepository; + private readonly ItSystemController _sut; + + public ItSystemControllerTest() + { + _authorizationContext = new Mock(); + _systemRepository = new Mock>(); + _sut = new ItSystemController( + _systemRepository.Object, + Mock.Of>(), + Mock.Of(), + new ReferenceService(), + _authorizationContext.Object + ); + + SetupControllerFrorTest(_sut); + } + + [Fact] + public void GetAccessRights_Returns_Forbidden_If_No_Access_In_Organization() + { + //Arrange + ExpectAllowReadInOrganization(false); + + //Act + var accessRights = _sut.GetAccessRights(true); + + //Assert + Assert.Equal(HttpStatusCode.Forbidden, accessRights.StatusCode); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void GetAccessRights_With_Organization_Access_Returns_Based_On_AccessRights(bool allowCreate) + { + //Arrange + ExpectAllowReadInOrganization(true); + ExpectAllowCreateReturns(allowCreate); + + //Act + var responseMessage = _sut.GetAccessRights(true); + + //Assert + var dto = ExpectResponseOf(responseMessage); + + Assert.True(dto.CanView); + Assert.Equal(allowCreate, dto.CanCreate); + } + + [Theory] + [InlineData(false, false, false)] + [InlineData(true, false, false)] + [InlineData(true, true, false)] + [InlineData(true, true, true)] + [InlineData(false, true, true)] + [InlineData(false, false, true)] + [InlineData(true, false, true)] + public void GetAccessRightsForEntity_Returns_Based_On_AccessRights(bool allowRead, bool allowModify, bool allowDelete) + { + //Arrange + var id = A(); + var itSystem = new ItSystem(); + _systemRepository.Setup(x => x.GetByKey(id)).Returns(itSystem); + ExpectAllowReadReturns(allowRead, itSystem); + ExpectAllowModifyReturns(allowModify, itSystem); + ExpectAllowDeleteReturns(allowDelete, itSystem); + + //Act + var responseMessage = _sut.GetAccessRightsForEntity(id,true); + + //Assert + var dto = ExpectResponseOf(responseMessage); + + Assert.Equal(allowRead, dto.CanView); + Assert.Equal(allowModify, dto.CanEdit); + Assert.Equal(allowDelete, dto.CanDelete); + } + + private void ExpectAllowDeleteReturns(bool allowDelete, ItSystem itSystem) + { + _authorizationContext.Setup(x => x.AllowDelete(itSystem)).Returns(allowDelete); + } + + private void ExpectAllowModifyReturns(bool allowModify, ItSystem itSystem) + { + _authorizationContext.Setup(x => x.AllowModify(itSystem)).Returns(allowModify); + } + + private void ExpectAllowReadReturns(bool allowRead, ItSystem itSystem) + { + _authorizationContext.Setup(x => x.AllowReads(itSystem)).Returns(allowRead); + } + + private void ExpectAllowCreateReturns(bool allowWrite) + { + _authorizationContext.Setup(x => x.AllowCreate()).Returns(allowWrite); + } + + private void ExpectAllowReadInOrganization(bool success) + { + _authorizationContext.Setup(x => x.GetOrganizationReadAccessLevel(It.IsAny())) + .Returns(success ? OrganizationDataReadAccessLevel.All : OrganizationDataReadAccessLevel.None); + } + } +} diff --git a/Tests.Unit.Presentation.Web/DomainServices/QueryAllByRestrictionCapabilitiesTest.cs b/Tests.Unit.Presentation.Web/DomainServices/QueryAllByRestrictionCapabilitiesTest.cs new file mode 100644 index 0000000000..febf0988fe --- /dev/null +++ b/Tests.Unit.Presentation.Web/DomainServices/QueryAllByRestrictionCapabilitiesTest.cs @@ -0,0 +1,76 @@ +using System; +using Core.DomainModel.ItContract; +using Core.DomainModel.ItProject; +using Core.DomainModel.ItSystem; +using Core.DomainModel.ItSystemUsage; +using Core.DomainModel.Organization; +using Core.DomainServices.Authorization; +using Core.DomainServices.Queries; +using Tests.Unit.Presentation.Web.Helpers; +using Xunit; + +namespace Tests.Unit.Presentation.Web.DomainServices +{ + public class QueryAllByRestrictionCapabilitiesTest : WithAutoFixture + { + [Theory] + [InlineData(typeof(ItSystem))] + [InlineData(typeof(ItSystemUsage))] + [InlineData(typeof(ItInterface))] + [InlineData(typeof(ItContract))] + [InlineData(typeof(ItProject))] + [InlineData(typeof(Organization))] + [InlineData(typeof(EconomyStream))] + public void RequiresPostFiltering_Returns_False_Full_Cross_Organizational_Read_Access(Type type) + { + //Arrange + var sut = CreateQuery(type, CrossOrganizationDataReadAccessLevel.All, A()); + + //Act + bool result = sut.RequiresPostFiltering(); + + //Assert + Assert.False(result); + } + + [Theory] + [InlineData(typeof(ItSystem), CrossOrganizationDataReadAccessLevel.Public, false)] + [InlineData(typeof(ItSystem), CrossOrganizationDataReadAccessLevel.None, false)] + [InlineData(typeof(ItSystemUsage), CrossOrganizationDataReadAccessLevel.Public, false)] + [InlineData(typeof(ItSystemUsage), CrossOrganizationDataReadAccessLevel.None, false)] + [InlineData(typeof(ItInterface), CrossOrganizationDataReadAccessLevel.Public, false)] + [InlineData(typeof(ItInterface), CrossOrganizationDataReadAccessLevel.None, false)] + [InlineData(typeof(ItContract), CrossOrganizationDataReadAccessLevel.Public, false)] + [InlineData(typeof(ItContract), CrossOrganizationDataReadAccessLevel.None, false)] + [InlineData(typeof(ItProject), CrossOrganizationDataReadAccessLevel.Public, false)] + [InlineData(typeof(ItProject), CrossOrganizationDataReadAccessLevel.None, false)] + [InlineData(typeof(Organization), CrossOrganizationDataReadAccessLevel.Public, false)] + [InlineData(typeof(EconomyStream), CrossOrganizationDataReadAccessLevel.Public, false)] + + //No IHasOrganization but has access modifier AND context aware AND sharing access is NONE and context aware does not support generic query since it only holds a method + [InlineData(typeof(Organization), CrossOrganizationDataReadAccessLevel.None, true)] + [InlineData(typeof(EconomyStream), CrossOrganizationDataReadAccessLevel.None, true)] + + public void RequiresPostFiltering_Returns(Type type, CrossOrganizationDataReadAccessLevel readAccess, bool expectedResult) + { + //Arrange + var sut = CreateQuery(type, readAccess, A()); + + //Act + bool result = sut.RequiresPostFiltering(); + + //Assert + Assert.Equal(expectedResult, result); + } + + private static dynamic CreateQuery(Type type, CrossOrganizationDataReadAccessLevel readAccess, int organizationId) + { + var constructor = + typeof(QueryAllByRestrictionCapabilities<>) + .MakeGenericType(type) + .GetConstructor(new[] {typeof(CrossOrganizationDataReadAccessLevel), typeof(int)}); + dynamic sut = constructor?.Invoke(new object[] {readAccess, organizationId}); + return sut; + } + } +} diff --git a/Tests.Unit.Presentation.Web/Hangfire/Hangfire_TestingWorkingcondition_ExpectedTestPass.cs b/Tests.Unit.Presentation.Web/Hangfire/Hangfire_TestingWorkingcondition_ExpectedTestPass.cs index f963cf6be8..294dbf9d7d 100644 --- a/Tests.Unit.Presentation.Web/Hangfire/Hangfire_TestingWorkingcondition_ExpectedTestPass.cs +++ b/Tests.Unit.Presentation.Web/Hangfire/Hangfire_TestingWorkingcondition_ExpectedTestPass.cs @@ -1,14 +1,6 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Hangfire; using NSubstitute; -using System.Web.Mvc; -using Hangfire.Common; -using Hangfire.States; -using Hangfire.Logging; using Xunit; namespace Tests.Unit.Presentation.Web.Hangfire diff --git a/Tests.Unit.Presentation.Web/Helpers/KitosRestControllerApiTestWithAutofixture.cs b/Tests.Unit.Presentation.Web/Helpers/KitosRestControllerApiTestWithAutofixture.cs new file mode 100644 index 0000000000..d7bb001ce8 --- /dev/null +++ b/Tests.Unit.Presentation.Web/Helpers/KitosRestControllerApiTestWithAutofixture.cs @@ -0,0 +1,65 @@ +using System.Net.Http; +using System.Security.Principal; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Hosting; +using Core.ApplicationServices; +using Core.DomainModel; +using Core.DomainServices; +using Moq; +using Presentation.Web.Controllers.API; +using Presentation.Web.Models; +using Xunit; + +namespace Tests.Unit.Presentation.Web.Helpers +{ + public abstract class KitosRestControllerApiTestWithAutofixture : WithAutoFixture + { + protected User KitosUser { get; private set; } + protected int CurrentOrganizationId { get; private set; } + + protected virtual void SetupControllerFrorTest(BaseApiController sut) + { + var userRepository = new Mock>(); + sut.UserRepository = userRepository.Object; + + //Set request context + var httpRequestMessage = new HttpRequestMessage(); + var httpRequestContext = new HttpRequestContext + { + Configuration = new HttpConfiguration() + }; + httpRequestMessage.Properties.Add(HttpPropertyKeys.RequestContextKey, httpRequestContext); + sut.RequestContext = httpRequestContext; + sut.Request = httpRequestMessage; + + //Setup authenticated user + var identity = new Mock(); + var userId = A(); + identity.Setup(x => x.Name).Returns(userId.ToString()); + var principal = new Mock(); + principal.Setup(x => x.Identity).Returns(identity.Object); + sut.User = principal.Object; + KitosUser = new User + { + DefaultOrganizationId = A() + }; + userRepository.Setup(x => x.GetByKey(userId)).Returns(KitosUser); + + //Set authenticated user + var authService = new Mock(); + CurrentOrganizationId = A(); + authService.Setup(x => x.GetCurrentOrganizationId(userId)).Returns(CurrentOrganizationId); + sut.AuthenticationService = authService.Object; + + } + + protected T ExpectResponseOf(HttpResponseMessage message) + { + var content = Assert.IsType>>(message.Content); + var dto = Assert.IsType>(content.Value); + Assert.NotNull(dto.Response); + return dto.Response; + } + } +} diff --git a/Tests.Unit.Presentation.Web/Helpers/WithAutoFixture.cs b/Tests.Unit.Presentation.Web/Helpers/WithAutoFixture.cs new file mode 100644 index 0000000000..22b9d192ea --- /dev/null +++ b/Tests.Unit.Presentation.Web/Helpers/WithAutoFixture.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using AutoFixture; + +namespace Tests.Unit.Presentation.Web.Helpers +{ + public abstract class WithAutoFixture + { + private readonly Fixture _fixture; + + protected WithAutoFixture() + { + _fixture = new Fixture(); + OnFixtureCreated(_fixture); + } + + protected virtual void OnFixtureCreated(Fixture fixture) + { + //Override to configure fixture-specific defaults + } + + protected T A() + { + return _fixture.Create(); + } + + protected IEnumerable Many(int? howMany = null) + { + return howMany.HasValue ? _fixture.CreateMany(howMany.Value) : _fixture.CreateMany(); + } + } +} diff --git a/Tests.Unit.Presentation.Web/Login/CustomMembershipProviderTest.cs b/Tests.Unit.Presentation.Web/Login/CustomMembershipProviderTest.cs index 7af7483df8..8ced92ea54 100644 --- a/Tests.Unit.Presentation.Web/Login/CustomMembershipProviderTest.cs +++ b/Tests.Unit.Presentation.Web/Login/CustomMembershipProviderTest.cs @@ -1,6 +1,7 @@ using System; using Core.DomainModel; using Core.DomainServices; +using Infrastructure.Services.Cryptography; using Ninject.Extensions.Logging; using Presentation.Web.Infrastructure; using Xunit; diff --git a/Tests.Unit.Presentation.Web/OData/AuthenticationServiceTest.cs b/Tests.Unit.Presentation.Web/OData/AuthenticationServiceTest.cs index 7eac4993ac..4279b22fc5 100644 --- a/Tests.Unit.Presentation.Web/OData/AuthenticationServiceTest.cs +++ b/Tests.Unit.Presentation.Web/OData/AuthenticationServiceTest.cs @@ -8,8 +8,6 @@ using Core.ApplicationServices; using Core.DomainModel; using Core.DomainModel.ItContract; -using Core.DomainModel.ItProject; -using Core.DomainModel.ItSystem; using Core.DomainModel.Organization; using Core.DomainModel.Reports; using Core.DomainServices; @@ -19,8 +17,6 @@ using Tests.Unit.Presentation.Web.Helpers; using Xunit; -//https://datatellblog.wordpress.com/2015/05/05/unit-testing-asp-net-mvc-authorization/ - namespace Tests.Unit.Presentation.Web.OData { public class AuthenticationServiceTest @@ -198,10 +194,10 @@ public void access_with_orgId_different_than_logedin_orgId_return_Forbidden() var result = _itContractsController.GetItContractsByOrgUnit(orgKey, 2); // assert - Assert.IsType(result); - var statusCode = result as StatusCodeResult; + Assert.IsType(result); + var statusCode = result as ResponseMessageResult; // ReSharper disable once PossibleNullReferenceException - Assert.True(statusCode.StatusCode == HttpStatusCode.Forbidden); + Assert.True(statusCode.Response.StatusCode == HttpStatusCode.Forbidden); } [Fact] diff --git a/Tests.Unit.Presentation.Web/OData/EconomyStreamsController.cs b/Tests.Unit.Presentation.Web/OData/EconomyStreamsController.cs index 32f92da2ed..ae4890c435 100644 --- a/Tests.Unit.Presentation.Web/OData/EconomyStreamsController.cs +++ b/Tests.Unit.Presentation.Web/OData/EconomyStreamsController.cs @@ -2,7 +2,9 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using System.Net; using System.Web.Http.Results; +using Core.ApplicationServices; using Core.DomainModel; using Core.DomainModel.ItContract; using Core.DomainModel.Organization; @@ -24,24 +26,25 @@ public ODataEconomyStreamsController() { _economyStreamRepository = Substitute.For>(); _userRepository = Substitute.For>(); - - _economyStreamsController = new EconomyStreamsController(_economyStreamRepository, _userRepository); + var _authenticator = Substitute.For(); + _economyStreamsController = new EconomyStreamsController(_economyStreamRepository, _authenticator, _userRepository); var userMock = new UserMock(_economyStreamsController, "12345678"); userMock.LogOn(); } [Fact] - public void GetByOrganization_NoAccess_ReturnUnauthorized() + public void GetByOrganization_NoAccess_ReturnForbidden() { // Arrange const int orgKey = 1; SetAccess(false, orgKey); // Act - var result = _economyStreamsController.GetByOrganization(orgKey); + var result = _economyStreamsController.GetByOrganization(orgKey) as ResponseMessageResult; // Assert - Assert.IsType(result); + Assert.IsType(result); + Assert.Equal(HttpStatusCode.Forbidden,result.Response.StatusCode); } [Fact] @@ -90,7 +93,7 @@ public void GetByOrganizationWithPublic_Access_ReturnOk() } [Fact] - public void GetByOrganizationWithLocal_NoAccess_ReturnUnauthorized() + public void GetByOrganizationWithLocal_NoAccess_ReturnForbidden() { // Arrange const int orgKey = 1; @@ -101,14 +104,15 @@ public void GetByOrganizationWithLocal_NoAccess_ReturnUnauthorized() .Returns(list); // Act - var result = _economyStreamsController.GetByOrganization(orgKey); + var result = _economyStreamsController.GetByOrganization(orgKey) as ResponseMessageResult; // Assert - Assert.IsType(result); + Assert.IsType(result); + Assert.Equal(HttpStatusCode.Forbidden, result.Response.StatusCode); } [Fact] - public void GetAllExtern_NoAccess_ReturnUnauthorized() + public void GetAllExtern_NoAccess_ReturnForbidden() { // Arrange const int orgKey = 1; @@ -116,10 +120,11 @@ public void GetAllExtern_NoAccess_ReturnUnauthorized() SetAccess(false, orgKey); // Act - var result = _economyStreamsController.GetAllExtern(orgKey, contractKey); + var result = _economyStreamsController.GetAllExtern(orgKey, contractKey) as ResponseMessageResult; // Assert - Assert.IsType(result); + Assert.IsType(result); + Assert.Equal(HttpStatusCode.Forbidden, result.Response.StatusCode); } [Fact] @@ -146,7 +151,7 @@ public void GetAllExtern_Access_ReturnOk() } [Fact] - public void GetAllIntern_NoAccess_ReturnUnauthorized() + public void GetAllIntern_NoAccess_ReturnForbidden() { // Arrange const int orgKey = 1; @@ -154,10 +159,11 @@ public void GetAllIntern_NoAccess_ReturnUnauthorized() SetAccess(false, orgKey); // Act - var result = _economyStreamsController.GetAllIntern(orgKey, contractKey); + var result = _economyStreamsController.GetAllIntern(orgKey, contractKey) as ResponseMessageResult; // Assert - Assert.IsType(result); + Assert.IsType(result); + Assert.Equal(HttpStatusCode.Forbidden, result.Response.StatusCode); } [Fact] @@ -184,7 +190,7 @@ public void GetAllIntern_Access_ReturnOk() } [Fact] - public void GetSingleExtern_NoAccess_ReturnUnauthorized() + public void GetSingleExtern_NoAccess_ReturnForbidden() { // Arrange const int orgKey = 1; @@ -193,10 +199,11 @@ public void GetSingleExtern_NoAccess_ReturnUnauthorized() SetAccess(false, orgKey); // Act - var result = _economyStreamsController.GetSingleExtern(orgKey, contractKey, key); + var result = _economyStreamsController.GetSingleExtern(orgKey, contractKey, key) as ResponseMessageResult; // Assert - Assert.IsType(result); + Assert.IsType(result); + Assert.Equal(HttpStatusCode.Forbidden, result.Response.StatusCode); } [Fact] @@ -224,7 +231,7 @@ public void GetSingleExtern_Access_ReturnOk() } [Fact] - public void GetSingleIntern_NoAccess_ReturnUnauthorized() + public void GetSingleIntern_NoAccess_ReturnForbidden() { // Arrange const int orgKey = 1; @@ -233,10 +240,11 @@ public void GetSingleIntern_NoAccess_ReturnUnauthorized() SetAccess(false, orgKey); // Act - var result = _economyStreamsController.GetSingleIntern(orgKey, contractKey, key); + var result = _economyStreamsController.GetSingleIntern(orgKey, contractKey, key) as ResponseMessageResult; // Assert - Assert.IsType(result); + Assert.IsType(result); + Assert.Equal(HttpStatusCode.Forbidden, result.Response.StatusCode); } [Fact] diff --git a/Tests.Unit.Presentation.Web/OData/RolesAndTypesPriorityTest.cs b/Tests.Unit.Presentation.Web/OData/RolesAndTypesPriorityTest.cs deleted file mode 100644 index 470932015d..0000000000 --- a/Tests.Unit.Presentation.Web/OData/RolesAndTypesPriorityTest.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Core.ApplicationServices; -using Core.DomainModel.ItProject; -using Core.DomainServices; -using NSubstitute; -using Presentation.Web.Controllers.OData.OptionControllers; -using Xunit; - -namespace Tests.Unit.Presentation.Web.OData -{ - public class RolesAndTypesPriorityTest - { - private readonly IGenericRepository _itProjectTypeMockRepository; - private readonly IAuthenticationService _iAuthenticationServiceMock; - private readonly ItProjectTypesController _itProjectTypesMockController; - private ItProjectType MockProjectType { get; set; } = new ItProjectType(); - - public RolesAndTypesPriorityTest() - { - _itProjectTypeMockRepository = Substitute.For>(); - _iAuthenticationServiceMock = Substitute.For(); - _itProjectTypesMockController = new ItProjectTypesController(_itProjectTypeMockRepository, _iAuthenticationServiceMock); - } - - [Fact] - public void Priority_should_increment_by_one() - { - //Arrange - // Get project type - //_itProjectTypesMockController.GetByOrganizationKey(1).Returns(MockProjectType); - //Act - //Assert - } - - [Fact] - public void Priority_should_decrement_by_one() - { - //Arrange - //Act - //Assert - } - - //public CustomMembershipProvider CustomMembershipProviderMock { get; set; } - //public User MockUser { get; set; } = new User(); - //public RolesAndTypesPriorityTest() - //{ - // // Setting up the necessary mocks - // CustomMembershipProviderMock = new CustomMembershipProvider() - // { - // UserRepositoryFactory = Substitute.For(), - // CryptoService = Substitute.For(), - // Logger = Substitute.For() - // }; - //} - - //[Fact] - //public void Should_Validate_User() - //{ - // // Arrange - // // Get an existing user - // CustomMembershipProviderMock.UserRepositoryFactory.GetUserRepository().GetByEmail("existingUser@kitos.dk").Returns(MockUser); - // // Set user password - // MockUser.Password = "thePassword"; - // // Helper method CheckPassord in ValidateUser -> Validate returns true if the password passed to ValidateUser is equal to the user objects password property - // CustomMembershipProviderMock.CryptoService.Encrypt("").ReturnsForAnyArgs("thePassword"); - - // // Act - // // Input existing user with valid password - // var success = CustomMembershipProviderMock.ValidateUser("existingUser@kitos.dk", "thePassword"); - - // // Assert - // Assert.True(success); - //} - } -} diff --git a/Tests.Unit.Presentation.Web/Tests.Unit.Presentation.Web.csproj b/Tests.Unit.Presentation.Web/Tests.Unit.Presentation.Web.csproj index 00810bcb4b..957429640c 100644 --- a/Tests.Unit.Presentation.Web/Tests.Unit.Presentation.Web.csproj +++ b/Tests.Unit.Presentation.Web/Tests.Unit.Presentation.Web.csproj @@ -34,34 +34,16 @@ prompt 4 - - true - ..\Output\WebUnitTest\ - DEBUG;TRACE - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\Test\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\Prod\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - + + ..\packages\AutoFixture.4.11.0\lib\net452\AutoFixture.dll + + + ..\packages\Castle.Core.4.4.0\lib\net45\Castle.Core.dll + + + ..\packages\Fare.2.1.1\lib\net35\Fare.dll + ..\packages\FluentAssertions.4.17.0\lib\net45\FluentAssertions.dll True @@ -78,33 +60,37 @@ ..\packages\Hangfire.SqlServer.1.6.6\lib\net45\Hangfire.SqlServer.dll True - - ..\packages\Microsoft.OData.Core.6.15.0\lib\portable-net45+win+wpa81\Microsoft.OData.Core.dll - True + + ..\packages\Microsoft.Extensions.DependencyInjection.1.0.0\lib\netstandard1.1\Microsoft.Extensions.DependencyInjection.dll - - ..\packages\Microsoft.OData.Edm.6.15.0\lib\portable-net45+win+wpa81\Microsoft.OData.Edm.dll - True + + ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.0.0\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll - - ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll - True + + ..\packages\Microsoft.OData.Core.7.5.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Core.dll + + + ..\packages\Microsoft.OData.Edm.7.5.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Edm.dll + + + ..\packages\Microsoft.Owin.4.0.0\lib\net451\Microsoft.Owin.dll ..\packages\Microsoft.Owin.Host.SystemWeb.3.0.0\lib\net45\Microsoft.Owin.Host.SystemWeb.dll True - - ..\packages\Microsoft.Spatial.6.15.0\lib\portable-net45+win+wpa81\Microsoft.Spatial.dll - True + + ..\packages\Microsoft.Spatial.7.5.0\lib\portable-net45+win8+wpa81\Microsoft.Spatial.dll ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll True - - ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll - True + + ..\packages\Moq.4.12.0\lib\net45\Moq.dll + + + ..\packages\Newtonsoft.Json.10.0.1\lib\net45\Newtonsoft.Json.dll ..\packages\Ninject.3.2.2.0\lib\net45-full\Ninject.dll @@ -122,7 +108,14 @@ ..\packages\Owin.1.0\lib\net40\Owin.dll True + + ..\packages\Serilog.1.5.14\lib\net45\Serilog.dll + + + ..\packages\Serilog.1.5.14\lib\net45\Serilog.FullNetFx.dll + + @@ -130,6 +123,12 @@ ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll True + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + + + ..\packages\System.Threading.Tasks.Extensions.4.5.1\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll + @@ -144,9 +143,8 @@ ..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll True - - ..\packages\Microsoft.AspNet.OData.5.9.1\lib\net45\System.Web.OData.dll - True + + ..\packages\Microsoft.AspNet.OData.6.0.0\lib\net45\System.Web.OData.dll ..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll @@ -194,12 +192,20 @@ + + + + + + + + + - @@ -224,6 +230,10 @@ {ADCACC1D-F538-464C-9102-F4C1D6FA35D3} Core.DomainServices + + {0326CAE6-87A1-4D66-84AE-EB8CE0340E9F} + Infrastructure.Services + {e75385a3-ea7c-4dff-b989-bee64bc506ed} Presentation.Web @@ -232,6 +242,7 @@ +