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

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

Merged
merged 11 commits into from
Oct 17, 2024
10 changes: 4 additions & 6 deletions bitwarden_license/src/Scim/Controllers/v2/UsersController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
}

[HttpGet("{id}")]
public async Task<IActionResult> Get(Guid organizationId, Guid id)

Check warning on line 47 in bitwarden_license/src/Scim/Controllers/v2/UsersController.cs

View workflow job for this annotation

GitHub Actions / Quality scan

ModelState.IsValid should be checked in controller actions. (https://rules.sonarsource.com/csharp/RSPEC-6967)

Check warning on line 47 in bitwarden_license/src/Scim/Controllers/v2/UsersController.cs

View workflow job for this annotation

GitHub Actions / Quality scan

ModelState.IsValid should be checked in controller actions. (https://rules.sonarsource.com/csharp/RSPEC-6967)
{
var orgUser = await _organizationUserRepository.GetDetailsByIdAsync(id);
if (orgUser == null || orgUser.OrganizationId != organizationId)
Expand All @@ -55,25 +55,23 @@
}

[HttpGet("")]
public async Task<IActionResult> Get(

Check warning on line 58 in bitwarden_license/src/Scim/Controllers/v2/UsersController.cs

View workflow job for this annotation

GitHub Actions / Quality scan

ModelState.IsValid should be checked in controller actions. (https://rules.sonarsource.com/csharp/RSPEC-6967)
Guid organizationId,
[FromQuery] string filter,
[FromQuery] int? count,
[FromQuery] int? startIndex)
[FromQuery] GetUsersQueryParamModel model)
{
var usersListQueryResult = await _getUsersListQuery.GetUsersListAsync(organizationId, filter, count, startIndex);
var usersListQueryResult = await _getUsersListQuery.GetUsersListAsync(organizationId, model);
var scimListResponseModel = new ScimListResponseModel<ScimUserResponseModel>
{
Resources = usersListQueryResult.userList.Select(u => new ScimUserResponseModel(u)).ToList(),
ItemsPerPage = count.GetValueOrDefault(usersListQueryResult.userList.Count()),
ItemsPerPage = model.Count,
TotalResults = usersListQueryResult.totalResults,
StartIndex = startIndex.GetValueOrDefault(1),
StartIndex = model.StartIndex,
};
return Ok(scimListResponseModel);
}

[HttpPost("")]
public async Task<IActionResult> Post(Guid organizationId, [FromBody] ScimUserRequestModel model)

Check warning on line 74 in bitwarden_license/src/Scim/Controllers/v2/UsersController.cs

View workflow job for this annotation

GitHub Actions / Quality scan

ModelState.IsValid should be checked in controller actions. (https://rules.sonarsource.com/csharp/RSPEC-6967)
{
var orgUser = await _postUserCommand.PostUserAsync(organizationId, model);
var scimUserResponseModel = new ScimUserResponseModel(orgUser);
Expand Down Expand Up @@ -115,7 +113,7 @@
}

[HttpDelete("{id}")]
public async Task<IActionResult> Delete(Guid organizationId, Guid id)

Check warning on line 116 in bitwarden_license/src/Scim/Controllers/v2/UsersController.cs

View workflow job for this annotation

GitHub Actions / Quality scan

ModelState.IsValid should be checked in controller actions. (https://rules.sonarsource.com/csharp/RSPEC-6967)
{
await _removeOrganizationUserCommand.RemoveUserAsync(organizationId, id, EventSystemUser.SCIM);
return new NoContentResult();
Expand Down
12 changes: 12 additions & 0 deletions bitwarden_license/src/Scim/Models/GetUserQueryParamModel.cs
Original file line number Diff line number Diff line change
@@ -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;
}
13 changes: 9 additions & 4 deletions bitwarden_license/src/Scim/Users/GetUsersListQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,16 @@ public GetUsersListQuery(IOrganizationUserRepository 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 usernameFilter = null;
string externalIdFilter = null;

int count = userQueryParams.Count;
int startIndex = userQueryParams.StartIndex;
string filter = userQueryParams.Filter;

if (!string.IsNullOrWhiteSpace(filter))
{
var filterLower = filter.ToLowerInvariant();
Expand Down Expand Up @@ -56,11 +61,11 @@ public GetUsersListQuery(IOrganizationUserRepository organizationUserRepository)
}
totalResults = userList.Count;
}
else if (string.IsNullOrWhiteSpace(filter) && startIndex.HasValue && count.HasValue)
else if (string.IsNullOrWhiteSpace(filter))
{
userList = orgUsers.OrderBy(ou => ou.Email)
.Skip(startIndex.Value - 1)
.Take(count.Value)
.Skip(startIndex - 1)
.Take(count)
.ToList();
totalResults = orgUsers.Count;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ namespace Bit.Scim.Users.Interfaces;

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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,46 @@ public async Task GetList_EmptyResult_Success()
AssertHelper.AssertPropertyEqual(expectedResponse, responseModel);
}

[Fact]
public async Task GetList_SearchUserNameWithoutOptionalParameters_Success()
{
string filter = "userName eq [email protected]";
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 = "[email protected]" }
},
Groups = new List<string>(),
Name = new BaseScimUserModel.NameModel("Test User 2"),
UserName = "[email protected]",
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]
public async Task Post_Success()
{
Expand Down
10 changes: 5 additions & 5 deletions bitwarden_license/test/Scim.Test/Users/GetUsersListQueryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public async Task GetUsersList_Success(int count, int startIndex, SutProvider<Ge
.GetManyDetailsByOrganizationAsync(organizationId)
.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);

Expand All @@ -49,7 +49,7 @@ public async Task GetUsersList_FilterUserName_Success(string email, SutProvider<
.GetManyDetailsByOrganizationAsync(organizationId)
.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);

Expand All @@ -71,7 +71,7 @@ public async Task GetUsersList_FilterUserName_Empty(string email, SutProvider<Ge
.GetManyDetailsByOrganizationAsync(organizationId)
.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);

Expand All @@ -96,7 +96,7 @@ public async Task GetUsersList_FilterExternalId_Success(SutProvider<GetUsersList
.GetManyDetailsByOrganizationAsync(organizationId)
.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);

Expand All @@ -120,7 +120,7 @@ public async Task GetUsersList_FilterExternalId_Empty(string externalId, SutProv
.GetManyDetailsByOrganizationAsync(organizationId)
.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);

Expand Down
Loading