Skip to content

Commit a13792d

Browse files
committed
fix(auth): allow project managers to list assignable users (role=User only) for task assignment
1 parent 3e1bf39 commit a13792d

2 files changed

Lines changed: 78 additions & 3 deletions

File tree

src/TaskManagement.Auth/Features/Users/UsersController.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,12 @@ public UsersController(
5454
/// <returns>A paged list of users.</returns>
5555
[Authorize(
5656
AuthenticationSchemes = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme,
57-
Roles = Roles.Administrator)]
57+
Roles = $"{Roles.Administrator},{Roles.ProjectManager}")]
5858
[EnableRateLimiting(RateLimitingPolicies.AdminUserManagement)]
5959
[HttpGet]
6060
[SwaggerOperation(
61-
Summary = "List users (admin)",
62-
Description = "Returns a paged admin-only list of users with optional filters.")]
61+
Summary = "List users (admin/project manager)",
62+
Description = "Returns a paged list of users with optional filters. Project managers are restricted to role=User queries.")]
6363
[ProducesResponseType(typeof(UserListResponse), StatusCodes.Status200OK)]
6464
[ProducesResponseType(StatusCodes.Status400BadRequest)]
6565
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
@@ -75,6 +75,17 @@ public async Task<ActionResult<UserListResponse>> GetUsers(
7575
[FromQuery] int? take,
7676
CancellationToken cancellationToken)
7777
{
78+
var isAdmin = User.IsInRole(Roles.Administrator);
79+
if (!isAdmin)
80+
{
81+
// Project managers can only list assignable contributors.
82+
var requestedRole = role?.Trim();
83+
if (!string.Equals(requestedRole, Roles.User, StringComparison.Ordinal))
84+
{
85+
return StatusCode(StatusCodes.Status403Forbidden);
86+
}
87+
}
88+
7889
var now = DateTimeOffset.UtcNow;
7990
var query = _userManager.Users.AsNoTracking();
8091

tests/TaskManagement.Auth.Tests/IntegrationTests/Features/Users/UsersEndpointTests.cs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,36 @@ public async Task GetUsers_WithSearch_ShouldReturnMatchingUsers()
176176
response.StatusCode.Should().Be(HttpStatusCode.Forbidden);
177177
}
178178

179+
[Fact]
180+
public async Task GetUsers_WhenProjectManagerAndRoleUser_ShouldReturnMatchingUsers()
181+
{
182+
await EnsureUserHasRoleAsync(TestData.User.Email, Roles.ProjectManager);
183+
var userRoleEmail = $"assignable-{Guid.NewGuid():N}@example.com";
184+
await EnsureUserWithRoleAsync(userRoleEmail, Roles.User);
185+
186+
var token = await GetAccessTokenAsync();
187+
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
188+
189+
var response = await _client.GetAsync($"/api/users?role={Roles.User}&page=1&pageSize=25");
190+
response.StatusCode.Should().Be(HttpStatusCode.OK);
191+
192+
var list = await response.Content.ReadFromJsonAsync<UserListResponse>();
193+
list.Should().NotBeNull();
194+
list!.Items.Should().NotBeEmpty();
195+
list.Items.Should().OnlyContain(u => u.Roles.Contains(Roles.User));
196+
}
197+
198+
[Fact]
199+
public async Task GetUsers_WhenProjectManagerWithoutRoleFilter_ShouldReturnForbidden()
200+
{
201+
await EnsureUserHasRoleAsync(TestData.User.Email, Roles.ProjectManager);
202+
var token = await GetAccessTokenAsync();
203+
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
204+
205+
var response = await _client.GetAsync("/api/users?page=1&pageSize=25");
206+
response.StatusCode.Should().Be(HttpStatusCode.Forbidden);
207+
}
208+
179209
[Fact]
180210
public async Task GetUsers_WithSearch_WhenAdmin_ShouldReturnMatchingUsers()
181211
{
@@ -531,6 +561,40 @@ private async Task<string> GetAdminAccessTokenAsync(string adminEmail, string ad
531561
return await GetAccessTokenAsync(adminEmail, adminPassword);
532562
}
533563

564+
private async Task EnsureUserWithRoleAsync(string email, string role, string password = "StrongPassword@123")
565+
{
566+
using var scope = _factory.Services.CreateScope();
567+
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
568+
var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
569+
570+
if (!await roleManager.RoleExistsAsync(role))
571+
{
572+
await roleManager.CreateAsync(new IdentityRole(role));
573+
}
574+
575+
var user = await userManager.FindByEmailAsync(email);
576+
if (user == null)
577+
{
578+
user = new ApplicationUser
579+
{
580+
UserName = email,
581+
Email = email,
582+
EmailConfirmed = true
583+
};
584+
var createResult = await userManager.CreateAsync(user, password);
585+
createResult.Succeeded.Should().BeTrue();
586+
}
587+
588+
if (!await userManager.IsInRoleAsync(user, role))
589+
{
590+
var addRoleResult = await userManager.AddToRoleAsync(user, role);
591+
addRoleResult.Succeeded.Should().BeTrue();
592+
}
593+
}
594+
595+
private Task EnsureUserHasRoleAsync(string email, string role)
596+
=> EnsureUserWithRoleAsync(email, role);
597+
534598
private static async Task<HttpResponseMessage> InitiateAuthorizationRequest(
535599
HttpClient client,
536600
AuthorizationParameters parameters)

0 commit comments

Comments
 (0)