1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-21 12:05:42 +01:00

[PM-12777] Fixed Issue #4034, API endpoint now handles optional parameters (#4812)

* resolves issue #4043 default values for itemsPerPage and startIndex

* UsersController#Get now uses a queryParamModel
Co-authored-by: Ahmad Mustafa Jebran <jebran.mustafa@gmail.com>
Co-authored-by: Luris Solis <solisluris@gmail.com>

* Test now passes, default 50 is represented

---------

Co-authored-by: Jared McCannon <jmccannon@bitwarden.com>
This commit is contained in:
Benson Bird 2024-10-17 08:03:26 -06:00 committed by GitHub
parent 7a509d20da
commit da0421890f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 71 additions and 16 deletions

View File

@ -57,17 +57,15 @@ public class UsersController : Controller
[HttpGet("")] [HttpGet("")]
public async Task<IActionResult> Get( public async Task<IActionResult> Get(
Guid organizationId, Guid organizationId,
[FromQuery] string filter, [FromQuery] GetUsersQueryParamModel model)
[FromQuery] int? count,
[FromQuery] int? startIndex)
{ {
var usersListQueryResult = await _getUsersListQuery.GetUsersListAsync(organizationId, filter, count, startIndex); var usersListQueryResult = await _getUsersListQuery.GetUsersListAsync(organizationId, model);
var scimListResponseModel = new ScimListResponseModel<ScimUserResponseModel> var scimListResponseModel = new ScimListResponseModel<ScimUserResponseModel>
{ {
Resources = usersListQueryResult.userList.Select(u => new ScimUserResponseModel(u)).ToList(), Resources = usersListQueryResult.userList.Select(u => new ScimUserResponseModel(u)).ToList(),
ItemsPerPage = count.GetValueOrDefault(usersListQueryResult.userList.Count()), ItemsPerPage = model.Count,
TotalResults = usersListQueryResult.totalResults, TotalResults = usersListQueryResult.totalResults,
StartIndex = startIndex.GetValueOrDefault(1), StartIndex = model.StartIndex,
}; };
return Ok(scimListResponseModel); return Ok(scimListResponseModel);
} }

View File

@ -0,0 +1,12 @@
using System.ComponentModel.DataAnnotations;
public class GetUsersQueryParamModel
{
public string Filter { get; init; } = string.Empty;
[Range(1, int.MaxValue)]
public int Count { get; init; } = 50;
[Range(1, int.MaxValue)]
public int StartIndex { get; init; } = 1;
}

View File

@ -13,11 +13,16 @@ public class GetUsersListQuery : IGetUsersListQuery
_organizationUserRepository = organizationUserRepository; _organizationUserRepository = organizationUserRepository;
} }
public async Task<(IEnumerable<OrganizationUserUserDetails> userList, int totalResults)> GetUsersListAsync(Guid organizationId, string filter, int? count, int? startIndex) public async Task<(IEnumerable<OrganizationUserUserDetails> userList, int totalResults)> GetUsersListAsync(Guid organizationId, GetUsersQueryParamModel userQueryParams)
{ {
string emailFilter = null; string emailFilter = null;
string usernameFilter = null; string usernameFilter = null;
string externalIdFilter = null; string externalIdFilter = null;
int count = userQueryParams.Count;
int startIndex = userQueryParams.StartIndex;
string filter = userQueryParams.Filter;
if (!string.IsNullOrWhiteSpace(filter)) if (!string.IsNullOrWhiteSpace(filter))
{ {
var filterLower = filter.ToLowerInvariant(); var filterLower = filter.ToLowerInvariant();
@ -56,11 +61,11 @@ public class GetUsersListQuery : IGetUsersListQuery
} }
totalResults = userList.Count; totalResults = userList.Count;
} }
else if (string.IsNullOrWhiteSpace(filter) && startIndex.HasValue && count.HasValue) else if (string.IsNullOrWhiteSpace(filter))
{ {
userList = orgUsers.OrderBy(ou => ou.Email) userList = orgUsers.OrderBy(ou => ou.Email)
.Skip(startIndex.Value - 1) .Skip(startIndex - 1)
.Take(count.Value) .Take(count)
.ToList(); .ToList();
totalResults = orgUsers.Count; totalResults = orgUsers.Count;
} }

View File

@ -4,5 +4,5 @@ namespace Bit.Scim.Users.Interfaces;
public interface IGetUsersListQuery public interface IGetUsersListQuery
{ {
Task<(IEnumerable<OrganizationUserUserDetails> userList, int totalResults)> GetUsersListAsync(Guid organizationId, string filter, int? count, int? startIndex); Task<(IEnumerable<OrganizationUserUserDetails> userList, int totalResults)> GetUsersListAsync(Guid organizationId, GetUsersQueryParamModel userQueryParams);
} }

View File

@ -236,6 +236,46 @@ public class UsersControllerTests : IClassFixture<ScimApplicationFactory>, IAsyn
AssertHelper.AssertPropertyEqual(expectedResponse, responseModel); AssertHelper.AssertPropertyEqual(expectedResponse, responseModel);
} }
[Fact]
public async Task GetList_SearchUserNameWithoutOptionalParameters_Success()
{
string filter = "userName eq user2@example.com";
int? itemsPerPage = null;
int? startIndex = null;
var expectedResponse = new ScimListResponseModel<ScimUserResponseModel>
{
ItemsPerPage = 50, //default value
TotalResults = 1,
StartIndex = 1, //default value
Resources = new List<ScimUserResponseModel>
{
new ScimUserResponseModel
{
Id = ScimApplicationFactory.TestOrganizationUserId2,
DisplayName = "Test User 2",
ExternalId = "UB",
Active = true,
Emails = new List<BaseScimUserModel.EmailModel>
{
new BaseScimUserModel.EmailModel { Primary = true, Type = "work", Value = "user2@example.com" }
},
Groups = new List<string>(),
Name = new BaseScimUserModel.NameModel("Test User 2"),
UserName = "user2@example.com",
Schemas = new List<string> { ScimConstants.Scim2SchemaUser }
}
},
Schemas = new List<string> { ScimConstants.Scim2SchemaListResponse }
};
var context = await _factory.UsersGetListAsync(ScimApplicationFactory.TestOrganizationId1, filter, itemsPerPage, startIndex);
Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
var responseModel = JsonSerializer.Deserialize<ScimListResponseModel<ScimUserResponseModel>>(context.Response.Body, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
AssertHelper.AssertPropertyEqual(expectedResponse, responseModel);
}
[Fact] [Fact]
public async Task Post_Success() public async Task Post_Success()
{ {

View File

@ -24,7 +24,7 @@ public class GetUsersListQueryTests
.GetManyDetailsByOrganizationAsync(organizationId) .GetManyDetailsByOrganizationAsync(organizationId)
.Returns(organizationUserUserDetails); .Returns(organizationUserUserDetails);
var result = await sutProvider.Sut.GetUsersListAsync(organizationId, null, count, startIndex); var result = await sutProvider.Sut.GetUsersListAsync(organizationId, new GetUsersQueryParamModel { Count = count, StartIndex = startIndex });
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).GetManyDetailsByOrganizationAsync(organizationId); await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).GetManyDetailsByOrganizationAsync(organizationId);
@ -49,7 +49,7 @@ public class GetUsersListQueryTests
.GetManyDetailsByOrganizationAsync(organizationId) .GetManyDetailsByOrganizationAsync(organizationId)
.Returns(organizationUserUserDetails); .Returns(organizationUserUserDetails);
var result = await sutProvider.Sut.GetUsersListAsync(organizationId, filter, null, null); var result = await sutProvider.Sut.GetUsersListAsync(organizationId, new GetUsersQueryParamModel { Filter = filter });
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).GetManyDetailsByOrganizationAsync(organizationId); await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).GetManyDetailsByOrganizationAsync(organizationId);
@ -71,7 +71,7 @@ public class GetUsersListQueryTests
.GetManyDetailsByOrganizationAsync(organizationId) .GetManyDetailsByOrganizationAsync(organizationId)
.Returns(organizationUserUserDetails); .Returns(organizationUserUserDetails);
var result = await sutProvider.Sut.GetUsersListAsync(organizationId, filter, null, null); var result = await sutProvider.Sut.GetUsersListAsync(organizationId, new GetUsersQueryParamModel { Filter = filter });
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).GetManyDetailsByOrganizationAsync(organizationId); await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).GetManyDetailsByOrganizationAsync(organizationId);
@ -96,7 +96,7 @@ public class GetUsersListQueryTests
.GetManyDetailsByOrganizationAsync(organizationId) .GetManyDetailsByOrganizationAsync(organizationId)
.Returns(organizationUserUserDetails); .Returns(organizationUserUserDetails);
var result = await sutProvider.Sut.GetUsersListAsync(organizationId, filter, null, null); var result = await sutProvider.Sut.GetUsersListAsync(organizationId, new GetUsersQueryParamModel { Filter = filter });
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).GetManyDetailsByOrganizationAsync(organizationId); await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).GetManyDetailsByOrganizationAsync(organizationId);
@ -120,7 +120,7 @@ public class GetUsersListQueryTests
.GetManyDetailsByOrganizationAsync(organizationId) .GetManyDetailsByOrganizationAsync(organizationId)
.Returns(organizationUserUserDetails); .Returns(organizationUserUserDetails);
var result = await sutProvider.Sut.GetUsersListAsync(organizationId, filter, null, null); var result = await sutProvider.Sut.GetUsersListAsync(organizationId, new GetUsersQueryParamModel { Filter = filter });
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).GetManyDetailsByOrganizationAsync(organizationId); await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).GetManyDetailsByOrganizationAsync(organizationId);