Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Roles #2336

Merged
merged 42 commits into from
Jul 11, 2023
Merged

Roles #2336

Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
b194264
Define roles
imnasnainaec Jun 29, 2023
83e1762
Fix Backend
imnasnainaec Jun 29, 2023
2bed823
Distinguish ProjectRole and Role
imnasnainaec Jun 29, 2023
fbc98a2
Match frontend to backend
imnasnainaec Jun 29, 2023
9af3bd8
Merge branch 'master' into roles
imnasnainaec Jun 29, 2023
59b7893
Merge branch 'master' into roles
imnasnainaec Jun 30, 2023
e5578a4
Merge branch 'master' into roles
imnasnainaec Jun 30, 2023
b483399
Merge branch 'master' into roles
imnasnainaec Jun 30, 2023
2b9d7cb
Obey CA2201
imnasnainaec Jun 30, 2023
f647a21
Change Manager -> Editor
imnasnainaec Jun 30, 2023
96aead6
Merge branch 'master' into roles
imnasnainaec Jul 4, 2023
7891c03
Finish frontend role/permission refactor
imnasnainaec Jul 4, 2023
bdffb1e
Fix tests
imnasnainaec Jul 4, 2023
4bd94fe
Add Editor option in project user management
imnasnainaec Jul 4, 2023
97ff6d3
Fix bugs
imnasnainaec Jul 4, 2023
1d26fef
Update maintenance scripts
imnasnainaec Jul 5, 2023
630c6dd
Finish backend role implementation
imnasnainaec Jul 5, 2023
c45d6fc
Clean up
imnasnainaec Jul 5, 2023
0ce3523
Clean up
imnasnainaec Jul 5, 2023
97a9594
Tidy
imnasnainaec Jul 5, 2023
41200a6
Merge branch 'master' into roles
imnasnainaec Jul 5, 2023
06d71c9
Add ProjectSettings component permissions tests
imnasnainaec Jul 6, 2023
fd2c90b
Tidy
imnasnainaec Jul 6, 2023
a1d8907
[Backend.Tests/Models] Expand tests
imnasnainaec Jul 6, 2023
a06fd03
Merge branch 'master' into roles
imnasnainaec Jul 6, 2023
1beef82
Expand UserRoleController tests
imnasnainaec Jul 6, 2023
814792c
Merge branch 'master' into roles
imnasnainaec Jul 7, 2023
9f9ae15
Merge branch 'master' into roles
imnasnainaec Jul 7, 2023
7b27731
Improve user-management menu
imnasnainaec Jul 7, 2023
a806aa7
Merge branch 'roles' of https://github.com/sillsdev/TheCombine into r…
imnasnainaec Jul 7, 2023
5655eae
Tidy
imnasnainaec Jul 7, 2023
cd4ff28
Match role names in strings
imnasnainaec Jul 7, 2023
dc3ecfb
Make list-equality check order-indifferent
imnasnainaec Jul 10, 2023
2c83cbc
Despecificate the README
imnasnainaec Jul 10, 2023
dfa3a4d
Merge branch 'master' into roles
imnasnainaec Jul 10, 2023
3520522
Safeguard against (e.g.) an admin changing an owner's role
imnasnainaec Jul 10, 2023
11605ce
Store Role enum as strings in mongo
imnasnainaec Jul 10, 2023
3d909de
Simplify canManageUser
imnasnainaec Jul 10, 2023
96279fd
Feed ActiveProjectUsers project id from parent, not local storage
imnasnainaec Jul 10, 2023
fd08c3b
Update add_user_to_proj args
imnasnainaec Jul 11, 2023
f9104be
tox -e fmt
imnasnainaec Jul 11, 2023
f25c0e3
Merge branch 'master' into roles
imnasnainaec Jul 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 120 additions & 62 deletions Backend.Tests/Controllers/UserRoleControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using BackendFramework.Controllers;
using BackendFramework.Interfaces;
using BackendFramework.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using NUnit.Framework;

Expand Down Expand Up @@ -47,35 +48,26 @@ public async Task Setup()
_projId = (await _projRepo.Create(new Project { Name = "UserRoleControllerTests" }))!.Id;
}

private UserRole RandomUserRole()
private UserRole RandomUserRole(Role role = Role.Harvester)
{
var userRole = new UserRole
{
ProjectId = _projId,
Permissions = new List<Permission>
{
Permission.DeleteEditSettingsAndUsers,
Permission.ImportExport,
Permission.MergeAndReviewEntries
}
};
return userRole;
return new UserRole { ProjectId = _projId, Role = role };
}

[Test]
public async Task TestGetAllUserRoles()
{
await _userRoleRepo.Create(RandomUserRole());
await _userRoleRepo.Create(RandomUserRole());
await _userRoleRepo.Create(RandomUserRole());
var roles = new List<Role> { Role.Harvester, Role.Editor, Role.Administrator };
foreach (var role in roles)
{
await _userRoleRepo.Create(RandomUserRole(role));
}

var getResult = await _userRoleController.GetProjectUserRoles(_projId);

Assert.IsInstanceOf<ObjectResult>(getResult);

var roles = ((ObjectResult)getResult).Value as List<UserRole>;
var userRoles = ((ObjectResult)getResult).Value as List<UserRole>;
Assert.That(roles, Has.Count.EqualTo(3));
(await _userRoleRepo.GetAllUserRoles(_projId)).ForEach(role => Assert.Contains(role, roles));
(await _userRoleRepo.GetAllUserRoles(_projId)).ForEach(ur => Assert.Contains(ur, userRoles));
}

[Test]
Expand All @@ -86,49 +78,96 @@ public async Task TestGetAllUserRolesMissingProject()
}

[Test]
public async Task TestGetAllUserRolesNoPermission()
public async Task TestGetAllUserRolesNotAuthorized()
{
_userRoleController.ControllerContext.HttpContext = PermissionServiceMock.UnauthorizedHttpContext();
var result = await _userRoleController.GetProjectUserRoles(_projId);
Assert.IsInstanceOf<ForbidResult>(result);
}

[Test]
public async Task TestGetUserRole()
public async Task TestGetCurrentPermissions()
{
var userRole = await _userRoleRepo.Create(RandomUserRole());
var user = await _userRepo.Create(new User());
_userRoleController.ControllerContext.HttpContext = PermissionServiceMock.HttpContextWithUserId(user!.Id);
user.ProjectRoles[_projId] = userRole.Id;
await _userRepo.Update(user.Id, user);

await _userRoleRepo.Create(RandomUserRole());
await _userRoleRepo.Create(RandomUserRole());

var action = await _userRoleController.GetUserRole(_projId, userRole.Id);
var action = await _userRoleController.GetCurrentPermissions(_projId);
Assert.IsInstanceOf<ObjectResult>(action);

var foundUserRole = ((ObjectResult)action).Value as UserRole;
Assert.AreEqual(userRole, foundUserRole);
var foundPermissions = ((ObjectResult)action).Value as List<Permission>;
var expectedPermissions = ProjectRole.RolePermissions(userRole.Role!);
Assert.AreEqual(expectedPermissions.Count, foundPermissions!.Count);
expectedPermissions.ForEach(p =>
{
Assert.Contains(p, foundPermissions);
});
}

[Test]
public async Task TestGetMissingUserRole()
public async Task TestGetCurrentPermissionsMissingUserRole()
{
var action = await _userRoleController.GetUserRole(_projId, MissingId);
Assert.IsInstanceOf<NotFoundObjectResult>(action);
var user = await _userRepo.Create(new User());
_userRoleController.ControllerContext.HttpContext = PermissionServiceMock.HttpContextWithUserId(user!.Id);
user.ProjectRoles[_projId] = "id-for-nonexistent-user-role";
await _userRepo.Update(user.Id, user);

var action = await _userRoleController.GetCurrentPermissions(_projId);
Assert.IsInstanceOf<ObjectResult>(action);

var foundPermissions = ((ObjectResult)action).Value as List<Permission>;
Assert.AreEqual(foundPermissions!.Count, 0);
}

[Test]
public async Task TestGetUserRolesMissingProject()
public async Task TestGetCurrentPermissionsMissingUserRoleId()
{
var userRole = await _userRoleRepo.Create(RandomUserRole());
var result = await _userRoleController.GetUserRole(MissingId, userRole.Id);
var user = await _userRepo.Create(new User());
_userRoleController.ControllerContext.HttpContext = PermissionServiceMock.HttpContextWithUserId(user!.Id);

var action = await _userRoleController.GetCurrentPermissions(_projId);
Assert.IsInstanceOf<ObjectResult>(action);

var foundPermissions = ((ObjectResult)action).Value as List<Permission>;
Assert.AreEqual(foundPermissions!.Count, 0);
}

[Test]
public async Task TestGetCurrentPermissionsMissingUser()
{
var result = await _userRoleController.GetCurrentPermissions(MissingId);
Assert.IsInstanceOf<NotFoundObjectResult>(result);
}

[Test]
public async Task TestGetUserRolesNoPermission()
public async Task TestGetCurrentPermissionsMissingProject()
{
_userRoleController.ControllerContext.HttpContext = PermissionServiceMock.UnauthorizedHttpContext();
var userRole = await _userRoleRepo.Create(RandomUserRole());
var result = await _userRoleController.GetUserRole(_projId, userRole.Id);
var user = await _userRepo.Create(new User());
_userRoleController.ControllerContext.HttpContext = PermissionServiceMock.HttpContextWithUserId(user!.Id);
var result = await _userRoleController.GetCurrentPermissions(MissingId);
Assert.IsInstanceOf<NotFoundObjectResult>(result);
}

[Test]
public async Task TestGetCurrentPermissionsNoUserInContext()
{
_userRoleController.ControllerContext.HttpContext = PermissionServiceMock.HttpContextWithUserId("");
var result = await _userRoleController.GetCurrentPermissions(_projId);
Assert.IsInstanceOf<ForbidResult>(result);
}

[Test]
public async Task TestGetCurrentPermissionsNotAuthorized()
{
var user = await _userRepo.Create(new User());
_userRoleController.ControllerContext.HttpContext =
PermissionServiceMock.UnauthorizedHttpContext(user!.Id);
var result = await _userRoleController.GetCurrentPermissions(_projId);
Assert.IsInstanceOf<ForbidResult>(result);
}

Expand Down Expand Up @@ -161,57 +200,76 @@ public async Task TestCreateUserRolesNoPermission()
[Test]
public async Task TestUpdateUserRole()
{
var userRole = RandomUserRole();
var userRole = RandomUserRole(Role.Harvester);
await _userRoleRepo.Create(userRole);
var user = new User { ProjectRoles = { [_projId] = userRole.Id } };
var userId = (await _userRepo.Create(user))!.Id;

var updatePermissions = userRole.Clone().Permissions;
updatePermissions.Add(Permission.WordEntry);

await _userRoleController.UpdateUserRolePermissions(_projId, userId, updatePermissions.ToArray());
var action = await _userRoleController.GetUserRole(_projId, userRole.Id);
var updatedUserRole = ((ObjectResult)action).Value as UserRole;
Assert.AreEqual(updatePermissions, updatedUserRole?.Permissions);
_userRoleController.ControllerContext.HttpContext = PermissionServiceMock.HttpContextWithUserId(userId);
var projectRole = new ProjectRole { ProjectId = _projId, Role = Role.Editor };
await _userRoleController.UpdateUserRole(userId, projectRole);
var action = await _userRoleController.GetCurrentPermissions(_projId);

var updatedPermissions = ((ObjectResult)action).Value as List<Permission>;
var expectedPermissions = ProjectRole.RolePermissions(projectRole.Role);
Assert.AreEqual(expectedPermissions.Count, updatedPermissions!.Count);
expectedPermissions.ForEach(p =>
{
Assert.Contains(p, updatedPermissions);
});
}

[Test]
public async Task TestCreateNewUpdateUserRole()
public async Task TestUpdateUserRoleNoChange()
{
var userRole = RandomUserRole();
var user = new User();
var userRole = RandomUserRole(Role.Harvester);
await _userRoleRepo.Create(userRole);
var user = new User { ProjectRoles = { [_projId] = userRole.Id } };
var userId = (await _userRepo.Create(user))!.Id;
_userRoleController.ControllerContext.HttpContext = PermissionServiceMock.HttpContextWithUserId(userId);
var projectRole = new ProjectRole { ProjectId = _projId, Role = userRole.Role };
var result = await _userRoleController.UpdateUserRole(userId, projectRole);
Assert.AreEqual(((ObjectResult)result).StatusCode, StatusCodes.Status304NotModified);
}

var updatePermissions = userRole.Clone().Permissions;
updatePermissions.Add(Permission.WordEntry);

var result = await _userRoleController.UpdateUserRolePermissions(_projId, userId, updatePermissions.ToArray());
[Test]
public async Task TestCreateNewUpdateUserRole()
{
var userId = (await _userRepo.Create(new User()))!.Id;
var projectRole = new ProjectRole { ProjectId = _projId, Role = Role.Editor };
var result = await _userRoleController.UpdateUserRole(userId, projectRole);
var newUserRoleId = (string)((OkObjectResult)result).Value!;
var action = await _userRoleController.GetUserRole(_projId, newUserRoleId);
var updatedUserRole = ((ObjectResult)action).Value as UserRole;
Assert.AreEqual(updatePermissions, updatedUserRole?.Permissions);
_userRoleController.ControllerContext.HttpContext = PermissionServiceMock.HttpContextWithUserId(userId);
var action = await _userRoleController.GetCurrentPermissions(_projId);

var updatedPermissions = ((ObjectResult)action).Value as List<Permission>;
var expectedPermissions = ProjectRole.RolePermissions(projectRole.Role);
Assert.AreEqual(expectedPermissions.Count, updatedPermissions!.Count);
expectedPermissions.ForEach(p =>
{
Assert.Contains(p, updatedPermissions);
});
}

[Test]
public async Task TestUpdateUserRolesMissingIds()
{
var userRole = RandomUserRole();
var projectResult = await _userRoleController.UpdateUserRolePermissions(
MissingId, userRole.Id, userRole.Permissions.ToArray());
Assert.IsInstanceOf<NotFoundObjectResult>(projectResult);
var projectRole = new ProjectRole { ProjectId = _projId, Role = Role.Editor };

var missingUserIdResult = await _userRoleController.UpdateUserRole(MissingId, projectRole);
Assert.IsInstanceOf<NotFoundObjectResult>(missingUserIdResult);

var userResult = await _userRoleController.UpdateUserRolePermissions(
_projId, MissingId, userRole.Permissions.ToArray());
Assert.IsInstanceOf<NotFoundObjectResult>(userResult);
var userRoleId = (await _userRoleRepo.Create(RandomUserRole(Role.Harvester))).Id;
projectRole.ProjectId = MissingId;
var missingProjIdResult = await _userRoleController.UpdateUserRole(userRoleId, projectRole);
Assert.IsInstanceOf<NotFoundObjectResult>(missingProjIdResult);
}

[Test]
public async Task TestUpdateUserRolesNoPermission()
{
_userRoleController.ControllerContext.HttpContext = PermissionServiceMock.UnauthorizedHttpContext();
var userRole = RandomUserRole();
var result = await _userRoleController.UpdateUserRolePermissions(
_projId, userRole.Id, userRole.Permissions.ToArray());
var userRoleId = (await _userRoleRepo.Create(RandomUserRole(Role.Harvester))).Id;
var result = await _userRoleController.UpdateUserRole(userRoleId, new ProjectRole());
Assert.IsInstanceOf<ForbidResult>(result);
}

Expand Down
31 changes: 30 additions & 1 deletion Backend.Tests/Mocks/PermissionServiceMock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,24 @@ public PermissionServiceMock(IUserRepository? userRepo = null)
/// <summary>
/// Generates an HttpContext that will fail permissions checks in the mock.
/// </summary>
public static HttpContext UnauthorizedHttpContext()
public static HttpContext UnauthorizedHttpContext(string? userId = null)
{
var httpContext = new DefaultHttpContext();
httpContext.Request.Headers["Authorization"] = UnauthorizedHeader;
if (userId is not null)
{
httpContext.Request.Headers["UserId"] = userId;
}
return httpContext;
}

/// <summary>
/// Generates an HttpContext with the specified userId.
/// </summary>
public static HttpContext HttpContextWithUserId(string userId)
{
var httpContext = new DefaultHttpContext();
httpContext.Request.Headers["UserId"] = userId;
return httpContext;
}

Expand Down Expand Up @@ -82,6 +96,21 @@ public bool HasProjectPermission(HttpContext request, Permission permission, str
return HasProjectPermission(request, permission).Result;
}

/// <summary>
/// By default this will return true, unless the test passes in an <see cref="UnauthorizedHttpContext"/>.
///
/// <param name="request">
/// Note this parameter is nullable in the mock implementation even though the real implementation it is not
/// to support unit testing when `HttpContext`s are not available.
/// </param>
/// <param name="role"> Same as the real implementation. </param>
/// <param name="projectId"> Same as the real implementation. </param>
/// </summary>
public bool ContainsProjectRole(HttpContext? request, Role role, string projectId)
{
return IsAuthorizedHttpContext(request);
}

public Task<bool> IsViolationEdit(HttpContext request, string userEditId, string projectId)
{
return Task.FromResult(false);
Expand Down
10 changes: 10 additions & 0 deletions Backend.Tests/Mocks/UserRoleRepositoryMock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@ public Task<bool> Delete(string projectId, string userRoleId)
public Task<ResultOfUpdate> Update(string userRoleId, UserRole userRole)
{
var foundUserRole = _userRoles.Single(ur => ur.Id == userRoleId);
if (foundUserRole is null)
{
return Task.FromResult(ResultOfUpdate.NotFound);
}

if (foundUserRole.ContentEquals(userRole))
{
return Task.FromResult(ResultOfUpdate.NoChange);
}

var success = _userRoles.Remove(foundUserRole);
if (!success)
{
Expand Down
Loading