private async Task RemoveUserFromExistingCosts(IList <string> removedLabels, UserBusinessRole userBusinessRole, CostUser user) { var costs = await GetQueryableCostsByUserObjectTypeAndLabels(userBusinessRole.ObjectType, removedLabels).ToListAsync(); foreach (var cost in costs) { await _permissionService.RevokeAccessForSubjectWithRole(cost.Id, user.Id, userBusinessRole.BusinessRole.RoleId, null, false); } }
public async Task AddUserToExistingCosts(CostUser user, UserBusinessRole userBusinessRole) { if (userBusinessRole.BusinessRole.Key == Constants.BusinessRole.RegionalAgencyUser && userBusinessRole.ObjectType == core.Constants.AccessObjectType.Region) { var costs = await _efContext.Cost .Where(c => c.Owner.Agency.GlobalAgencyRegionId != null) .Where(c => userBusinessRole.LocationIds.Contains(c.Owner.Agency.GlobalAgencyRegion.Id.ToString())) .Select(c => new { costId = c.Id, agencyRegion = c.Owner.Agency.GlobalAgencyRegion.Region }) .ToListAsync(); foreach (var cost in costs) { await _permissionService.GrantUserAccess <Cost>( userBusinessRole.BusinessRole.RoleId, cost.costId, user, BuType.Pg, null, cost.agencyRegion, false); } } else { var queryableCosts = GetQueryableCostsByUserObjectTypeAndLabels(userBusinessRole.ObjectType, userBusinessRole.Labels); if (queryableCosts == null) { return; } var costs = await queryableCosts .Include(c => c.LatestCostStageRevision) .ThenInclude(csr => csr.StageDetails) .ToArrayAsync(); foreach (var cost in costs) { var costStageDetails = _costStageRevisionService.GetStageDetails <PgStageDetailsForm>(cost.LatestCostStageRevision); switch (userBusinessRole.ObjectType) { case core.Constants.AccessObjectType.Smo: if (!user.UserUserGroups.Any() || !user.UserUserGroups.Any(uug => uug.UserGroup.Label == costStageDetails.SmoName && uug.UserGroup.ObjectId == cost.Id)) { await _permissionService.GrantUserAccess <Cost>( userBusinessRole.BusinessRole.RoleId, cost.Id, user, BuType.Pg, null, costStageDetails.SmoName, false); } break; case core.Constants.AccessObjectType.Region: if (!user.UserUserGroups.Any() || user.UserUserGroups.Any(uug => uug.UserGroup.Label != costStageDetails.BudgetRegion.Name)) { await _permissionService.GrantUserAccess <Cost>( userBusinessRole.BusinessRole.RoleId, cost.Id, user, BuType.Pg, null, costStageDetails.BudgetRegion.Name, false); } break; } } } }
public async Task Add_When_UserIsAgencyAdmin_Should_HaveAccessToAssignRolesToUserInTheSameAgencyAsTheyAre() { // Arrange var usersBusinessRole = _businessRoles.First(a => a.Value == Constants.BusinessRole.AgencyAdmin); var selectedBusinessRole = _businessRoles.First(a => a.Value == Constants.BusinessRole.AgencyAdmin); var userBeingUpdated = _users.First(user => user.Id == _userNoRoles); var userPerformingAction = _users.First(u => u.Id == UserWithAgencyAdminRoleId); var updateUserModel = new UpdateUserModel { AccessDetails = new List <AccessDetail> { new AccessDetail { BusinessRoleId = selectedBusinessRole.Id } } }; userPerformingAction.Agency.Id = userBeingUpdated.Agency.Id; var userBusinessRole = new UserBusinessRole { CostUserId = userPerformingAction.Id, BusinessRoleId = usersBusinessRole.Id, CostUser = userPerformingAction }; EfContext.AbstractType.AddRange(_abstractTypes); EfContext.Role.AddRange(_roles); EfContext.BusinessRole.AddRange(_businessRoles); EfContext.CostUser.AddRange(_users); EfContext.Cost.AddRange(_costs); EfContext.UserBusinessRole.Add(userBusinessRole); EfContext.SaveChanges(); _permissionServiceMock.Setup(a => a.CheckHasAccess(UserWithAgencyAdminRoleId, userBeingUpdated.Id, AclActionType.Edit, "user")).ReturnsAsync(false); _pgAgencyServiceMock.Setup(a => a.GetOrCreatePseudoAgencies(It.IsAny <Agency[]>())) .ReturnsAsync( _abstractTypes .Where(at => at.Type == core.Constants.AccessObjectType.Agency) .Select(a => new AbstractType()) .ToList() ); // Act await _pgUserService.UpdateUser(new UserIdentity { Id = UserWithAgencyAdminRoleId }, userBeingUpdated.Id, updateUserModel, BuType.Pg); // Assert userBeingUpdated.UserBusinessRoles.Count.Should().BeGreaterThan(0); userBeingUpdated.UserBusinessRoles.First().BusinessRole.Id.Should().Be(selectedBusinessRole.Id); }
public async Task <OperationResponse> UpdateUser(UserIdentity userIdentity, Guid id, UpdateUserModel updateUserModel, BuType buType) { var xUserId = userIdentity.Id; var dbUser = _efContext.CostUser .Include(cu => cu.Agency) .ThenInclude(a => a.GlobalAgencyRegion) .ThenInclude(gar => gar.GlobalAgency) .Include(a => a.UserBusinessRoles) .ThenInclude(ubr => ubr.BusinessRole) .Include(a => a.UserUserGroups) .ThenInclude(uug => uug.UserGroup) .SingleOrDefault(a => a.Id == id); var userPerformingAction = _efContext.CostUser .Include(cu => cu.Agency) .Include(a => a.UserBusinessRoles) .ThenInclude(ubr => ubr.BusinessRole) .ThenInclude(br => br.Role) .SingleOrDefault(a => a.Id == xUserId); var businessRoles = _efContext.BusinessRole.ToList(); if (dbUser == null) { return(new OperationResponse(false, "User doesn't exist.")); } var hasAccess = await _permissionService.CheckHasAccess(xUserId, id, AclActionType.Edit, "user"); if (!hasAccess) { hasAccess = userPerformingAction.Agency.Id == dbUser.Agency.Id && userPerformingAction.UserBusinessRoles.Any(ubr => ubr.BusinessRole.Key == Constants.BusinessRole.AgencyAdmin); } SetMissingObjectId(updateUserModel.AccessDetails, businessRoles, out var abstractType); if (updateUserModel.AccessDetails.Count > 0 && dbUser.UserBusinessRoles.Count > 0) { updateUserModel.AccessDetails = GetAllAccessDetails(updateUserModel.AccessDetails, dbUser.UserBusinessRoles); } //This is done because if you dont it will keep its reference and cause issues lower in this function var toBeRemoved = dbUser.UserBusinessRoles .Where(ubr => !updateUserModel.AccessDetails.Any(ad => ubr.BusinessRoleId == ad.BusinessRoleId && ubr.ObjectType == ad.ObjectType && (ubr.ObjectId == ad.ObjectId || ubr.ObjectId == ad.OriginalObjectId || ubr.Labels.Contains(ad.LabelName) ) || ad.ObjectType == core.Constants.AccessObjectType.Client && ubr.ObjectType == ad.ObjectType && ubr.BusinessRoleId == ad.BusinessRoleId ) ) .ToList(); var entries = new List <IActivityLogEntry>(); await _efContext.InTransactionAsync(async() => { await RemoveUserFromExistingCosts(toBeRemoved, dbUser); await UpdateBusinessRoleLabels(updateUserModel.AccessDetails, dbUser); var agenciesToUpdateInElastic = new List <AbstractType>(); //Add New Access Rules foreach (var accessDetail in updateUserModel.AccessDetails) { if (!hasAccess) { hasAccess = userPerformingAction.UserBusinessRoles.Any(ubr => new[] { Roles.PlatformOwner, Roles.ClientAdmin, Roles.AdstreamAdmin }.Contains(ubr.BusinessRole.Role.Name) && ubr.ObjectId == accessDetail.ObjectId && ubr.CostUserId == xUserId ); if (!hasAccess) { // TODO: show the user which rules have been skipped _logger.Warning( $"User {userPerformingAction.Email}|{userPerformingAction.Id} does not have access to alter roles for {dbUser.Email}|{dbUser.Id} in scope of {accessDetail.ObjectType}|{accessDetail.ObjectId}, skipping this rule!"); continue; } } var selectedBusinessRole = businessRoles.SingleOrDefault(a => a.Id == accessDetail.BusinessRoleId); if (selectedBusinessRole == null) { throw new Exception($"Couldn't find business role with id {accessDetail.BusinessRoleId}"); } if (dbUser.UserBusinessRoles.Any(userBusinessRole => userBusinessRole != null && userBusinessRole.BusinessRole.Id == accessDetail.BusinessRoleId && userBusinessRole.ObjectType == accessDetail.ObjectType && (string.IsNullOrEmpty(accessDetail.LabelName) || userBusinessRole.Labels.Contains(accessDetail.LabelName)))) { //Break out if user already has access. _logger.Information( $"User {dbUser.Id} already has access to {accessDetail.ObjectType} | {accessDetail.ObjectId} with BusinessRole {selectedBusinessRole.Key}|{selectedBusinessRole.Id}"); continue; } if (accessDetail.ObjectType == core.Constants.AccessObjectType.Smo || accessDetail.ObjectType == core.Constants.AccessObjectType.Region && selectedBusinessRole.Key != Constants.BusinessRole.RegionalAgencyUser) { _logger.Information($"User {dbUser.Email}|{dbUser.Id} will be given access at cost creation."); var userBusinessRole = dbUser.UserBusinessRoles.FirstOrDefault(ubr => ubr.ObjectType == accessDetail.ObjectType); if (userBusinessRole != null) { var labels = userBusinessRole.Labels.ToList(); labels.Add(accessDetail.LabelName); userBusinessRole.Labels = labels.Distinct().ToArray(); } else { userBusinessRole = new UserBusinessRole(xUserId) { BusinessRole = selectedBusinessRole, Labels = new[] { accessDetail.LabelName }, ObjectType = accessDetail.ObjectType }; dbUser.UserBusinessRoles.Add(userBusinessRole); entries.Add(new UserRoleAssigned(dbUser.Email, selectedBusinessRole.Key, userIdentity)); await AddUserToExistingCosts(dbUser, userBusinessRole); } continue; } if (accessDetail.ObjectType == core.Constants.AccessObjectType.Region && selectedBusinessRole.Key == Constants.BusinessRole.RegionalAgencyUser) { _logger.Information($"User {dbUser.Email}|{dbUser.Id} will be given access at cost creation."); var userBusinessRole = dbUser.UserBusinessRoles.FirstOrDefault(ubr => ubr.ObjectType == accessDetail.ObjectType); if (userBusinessRole != null) { var labelIds = userBusinessRole.LocationIds.ToList(); labelIds.Add(accessDetail.LabelId.ToString()); userBusinessRole.LocationIds = labelIds.Distinct().ToArray(); var labels = userBusinessRole.Labels.ToList(); labels.Add(accessDetail.LabelName); userBusinessRole.Labels = labels.Distinct().ToArray(); } else { userBusinessRole = new UserBusinessRole(xUserId) { BusinessRole = selectedBusinessRole, Labels = new[] { accessDetail.LabelName }, LocationIds = new[] { accessDetail.LabelId?.ToString() }, ObjectType = accessDetail.ObjectType }; dbUser.UserBusinessRoles.Add(userBusinessRole); entries.Add(new UserRoleAssigned(dbUser.Email, selectedBusinessRole.Key, userIdentity)); await AddUserToExistingCosts(dbUser, userBusinessRole); } continue; } if (selectedBusinessRole.Key == Constants.BusinessRole.AdstreamAdmin || dbUser.Agency.Labels.Contains(Constants.Agency.PgOwnerLabel) || dbUser.Agency.Labels.Contains(Constants.Agency.AdstreamOwnerLabel) || (abstractType.Module != null && abstractType.Module?.ClientType == ClientType.Root)) { abstractType = await _efContext.AbstractType.FirstOrDefaultAsync(at => at.Id == accessDetail.ObjectId); var labels = abstractType.Module?.ClientType == ClientType.Root ? new[] { ClientType.Root.ToString() } : new[] { core.Constants.AccessObjectType.Client }; await GrantAccessToAbstractType(new [] { abstractType }, xUserId, buType, dbUser, selectedBusinessRole, accessDetail, labels); entries.Add(new UserRoleAssigned(dbUser.Email, selectedBusinessRole.Key, userIdentity)); agenciesToUpdateInElastic.Add(abstractType); } else if (selectedBusinessRole.Key == Constants.BusinessRole.CostConsultant) { // If business role is "Cost Consultant" the user doesn't get access to any object at this stage. dbUser.UserBusinessRoles.Add(new UserBusinessRole(xUserId) { BusinessRole = selectedBusinessRole, ObjectType = accessDetail.ObjectType }); entries.Add(new UserRoleAssigned(dbUser.Email, selectedBusinessRole.Key, userIdentity)); } else { var agencyToCreatePseudoAgencies = new[] { dbUser.Agency }; if (selectedBusinessRole.Key == Constants.BusinessRole.RegionalAgencyUser) { // TODO: use id of GlobalAgencyRegion in accessDetail rather then label because different Agencies can have regions with the same name. The same for Budget region. agencyToCreatePseudoAgencies = await _efContext.GlobalAgencyRegion .Where(gar => gar.Region == accessDetail.LabelName && gar.GlobalAgencyId == dbUser.Agency.GlobalAgencyRegion.GlobalAgencyId) .SelectMany(gar => gar.GlobalAgency.GlobalAgencyRegions) .SelectMany(gar => gar.Agencies) .ToArrayAsync(); } var pseudoAgencies = await _pgAgencyService.GetOrCreatePseudoAgencies(agencyToCreatePseudoAgencies); await GrantAccessToAbstractType(pseudoAgencies, xUserId, buType, dbUser, selectedBusinessRole, accessDetail); agenciesToUpdateInElastic.AddRange(pseudoAgencies); } } dbUser.ApprovalLimit = updateUserModel.ApprovalLimit; dbUser.EmailUrl = updateUserModel.EmailOverride; dbUser.NotificationBudgetRegionId = updateUserModel.NotificationBudgetRegionId; dbUser.UserGroups = await _permissionService.GetObjectUserGroups(dbUser.Id, null); await _activityLogService.LogRange(entries); _eventService.Add(new CostUsersUpdated(_mapper.Map <CostUserSearchItem[]>(new [] { dbUser }))); if (agenciesToUpdateInElastic.Any()) { _eventService.Add(new AgenciesUpdated(_mapper.Map <AgencySearchItem[]>(agenciesToUpdateInElastic))); } }, async() => await _eventService.SendAllPendingAsync()); return(new OperationResponse(true, "User updated.")); }