1
0
mirror of https://github.com/bitwarden/server synced 2025-12-06 00:03:34 +00:00

[PM-28871] Default startIndex and count values on SCIM groups list API (#6648)

* default startindex and count values on SCIM groups list api

* convert params to a model, like users

* review feedback

* fix file name to be plural

* added integration test
This commit is contained in:
Kyle Spearrin
2025-12-03 10:52:09 -05:00
committed by GitHub
parent 28e9c24f33
commit 1566a6d587
10 changed files with 74 additions and 16 deletions

View File

@@ -61,17 +61,15 @@ public class GroupsController : Controller
[HttpGet("")] [HttpGet("")]
public async Task<IActionResult> Get( public async Task<IActionResult> Get(
Guid organizationId, Guid organizationId,
[FromQuery] string filter, [FromQuery] GetGroupsQueryParamModel model)
[FromQuery] int? count,
[FromQuery] int? startIndex)
{ {
var groupsListQueryResult = await _getGroupsListQuery.GetGroupsListAsync(organizationId, filter, count, startIndex); var groupsListQueryResult = await _getGroupsListQuery.GetGroupsListAsync(organizationId, model);
var scimListResponseModel = new ScimListResponseModel<ScimGroupResponseModel> var scimListResponseModel = new ScimListResponseModel<ScimGroupResponseModel>
{ {
Resources = groupsListQueryResult.groupList.Select(g => new ScimGroupResponseModel(g)).ToList(), Resources = groupsListQueryResult.groupList.Select(g => new ScimGroupResponseModel(g)).ToList(),
ItemsPerPage = count.GetValueOrDefault(groupsListQueryResult.groupList.Count()), ItemsPerPage = model.Count,
TotalResults = groupsListQueryResult.totalResults, TotalResults = groupsListQueryResult.totalResults,
StartIndex = startIndex.GetValueOrDefault(1), StartIndex = model.StartIndex,
}; };
return Ok(scimListResponseModel); return Ok(scimListResponseModel);
} }

View File

@@ -4,6 +4,7 @@
using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Repositories;
using Bit.Scim.Groups.Interfaces; using Bit.Scim.Groups.Interfaces;
using Bit.Scim.Models;
namespace Bit.Scim.Groups; namespace Bit.Scim.Groups;
@@ -16,10 +17,16 @@ public class GetGroupsListQuery : IGetGroupsListQuery
_groupRepository = groupRepository; _groupRepository = groupRepository;
} }
public async Task<(IEnumerable<Group> groupList, int totalResults)> GetGroupsListAsync(Guid organizationId, string filter, int? count, int? startIndex) public async Task<(IEnumerable<Group> groupList, int totalResults)> GetGroupsListAsync(
Guid organizationId, GetGroupsQueryParamModel groupQueryParams)
{ {
string nameFilter = null; string nameFilter = null;
string externalIdFilter = null; string externalIdFilter = null;
int count = groupQueryParams.Count;
int startIndex = groupQueryParams.StartIndex;
string filter = groupQueryParams.Filter;
if (!string.IsNullOrWhiteSpace(filter)) if (!string.IsNullOrWhiteSpace(filter))
{ {
if (filter.StartsWith("displayName eq ")) if (filter.StartsWith("displayName eq "))
@@ -53,11 +60,11 @@ public class GetGroupsListQuery : IGetGroupsListQuery
} }
totalResults = groupList.Count; totalResults = groupList.Count;
} }
else if (string.IsNullOrWhiteSpace(filter) && startIndex.HasValue && count.HasValue) else if (string.IsNullOrWhiteSpace(filter))
{ {
groupList = groups.OrderBy(g => g.Name) groupList = groups.OrderBy(g => g.Name)
.Skip(startIndex.Value - 1) .Skip(startIndex - 1)
.Take(count.Value) .Take(count)
.ToList(); .ToList();
totalResults = groups.Count; totalResults = groups.Count;
} }

View File

@@ -1,8 +1,9 @@
using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Scim.Models;
namespace Bit.Scim.Groups.Interfaces; namespace Bit.Scim.Groups.Interfaces;
public interface IGetGroupsListQuery public interface IGetGroupsListQuery
{ {
Task<(IEnumerable<Group> groupList, int totalResults)> GetGroupsListAsync(Guid organizationId, string filter, int? count, int? startIndex); Task<(IEnumerable<Group> groupList, int totalResults)> GetGroupsListAsync(Guid organizationId, GetGroupsQueryParamModel model);
} }

View File

@@ -0,0 +1,14 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Scim.Models;
public class GetGroupsQueryParamModel
{
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

@@ -1,5 +1,7 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
namespace Bit.Scim.Models;
public class GetUsersQueryParamModel public class GetUsersQueryParamModel
{ {
public string Filter { get; init; } = string.Empty; public string Filter { get; init; } = string.Empty;

View File

@@ -3,6 +3,7 @@
using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Scim.Models;
using Bit.Scim.Users.Interfaces; using Bit.Scim.Users.Interfaces;
namespace Bit.Scim.Users; namespace Bit.Scim.Users;

View File

@@ -1,4 +1,5 @@
using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Scim.Models;
namespace Bit.Scim.Users.Interfaces; namespace Bit.Scim.Users.Interfaces;

View File

@@ -200,6 +200,38 @@ public class GroupsControllerTests : IClassFixture<ScimApplicationFactory>, IAsy
AssertHelper.AssertPropertyEqual(expectedResponse, responseModel); AssertHelper.AssertPropertyEqual(expectedResponse, responseModel);
} }
[Fact]
public async Task GetList_SearchDisplayNameWithoutOptionalParameters_Success()
{
string filter = "displayName eq Test Group 2";
int? itemsPerPage = null;
int? startIndex = null;
var expectedResponse = new ScimListResponseModel<ScimGroupResponseModel>
{
ItemsPerPage = 50, //default value
TotalResults = 1,
StartIndex = 1, //default value
Resources = new List<ScimGroupResponseModel>
{
new ScimGroupResponseModel
{
Id = ScimApplicationFactory.TestGroupId2,
DisplayName = "Test Group 2",
ExternalId = "B",
Schemas = new List<string> { ScimConstants.Scim2SchemaGroup }
}
},
Schemas = new List<string> { ScimConstants.Scim2SchemaListResponse }
};
var context = await _factory.GroupsGetListAsync(ScimApplicationFactory.TestOrganizationId1, filter, itemsPerPage, startIndex);
Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
var responseModel = JsonSerializer.Deserialize<ScimListResponseModel<ScimGroupResponseModel>>(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

@@ -1,6 +1,7 @@
using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Repositories;
using Bit.Scim.Groups; using Bit.Scim.Groups;
using Bit.Scim.Models;
using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.AutoFixture.Attributes;
using Bit.Test.Common.Helpers; using Bit.Test.Common.Helpers;
@@ -24,7 +25,7 @@ public class GetGroupsListCommandTests
.GetManyByOrganizationIdAsync(organizationId) .GetManyByOrganizationIdAsync(organizationId)
.Returns(groups); .Returns(groups);
var result = await sutProvider.Sut.GetGroupsListAsync(organizationId, null, count, startIndex); var result = await sutProvider.Sut.GetGroupsListAsync(organizationId, new GetGroupsQueryParamModel { Count = count, StartIndex = startIndex });
AssertHelper.AssertPropertyEqual(groups.Skip(startIndex - 1).Take(count).ToList(), result.groupList); AssertHelper.AssertPropertyEqual(groups.Skip(startIndex - 1).Take(count).ToList(), result.groupList);
AssertHelper.AssertPropertyEqual(groups.Count, result.totalResults); AssertHelper.AssertPropertyEqual(groups.Count, result.totalResults);
@@ -47,7 +48,7 @@ public class GetGroupsListCommandTests
.GetManyByOrganizationIdAsync(organizationId) .GetManyByOrganizationIdAsync(organizationId)
.Returns(groups); .Returns(groups);
var result = await sutProvider.Sut.GetGroupsListAsync(organizationId, filter, null, null); var result = await sutProvider.Sut.GetGroupsListAsync(organizationId, new GetGroupsQueryParamModel { Filter = filter });
AssertHelper.AssertPropertyEqual(expectedGroupList, result.groupList); AssertHelper.AssertPropertyEqual(expectedGroupList, result.groupList);
AssertHelper.AssertPropertyEqual(expectedTotalResults, result.totalResults); AssertHelper.AssertPropertyEqual(expectedTotalResults, result.totalResults);
@@ -67,7 +68,7 @@ public class GetGroupsListCommandTests
.GetManyByOrganizationIdAsync(organizationId) .GetManyByOrganizationIdAsync(organizationId)
.Returns(groups); .Returns(groups);
var result = await sutProvider.Sut.GetGroupsListAsync(organizationId, filter, null, null); var result = await sutProvider.Sut.GetGroupsListAsync(organizationId, new GetGroupsQueryParamModel { Filter = filter });
AssertHelper.AssertPropertyEqual(expectedGroupList, result.groupList); AssertHelper.AssertPropertyEqual(expectedGroupList, result.groupList);
AssertHelper.AssertPropertyEqual(expectedTotalResults, result.totalResults); AssertHelper.AssertPropertyEqual(expectedTotalResults, result.totalResults);
@@ -90,7 +91,7 @@ public class GetGroupsListCommandTests
.GetManyByOrganizationIdAsync(organizationId) .GetManyByOrganizationIdAsync(organizationId)
.Returns(groups); .Returns(groups);
var result = await sutProvider.Sut.GetGroupsListAsync(organizationId, filter, null, null); var result = await sutProvider.Sut.GetGroupsListAsync(organizationId, new GetGroupsQueryParamModel { Filter = filter });
AssertHelper.AssertPropertyEqual(expectedGroupList, result.groupList); AssertHelper.AssertPropertyEqual(expectedGroupList, result.groupList);
AssertHelper.AssertPropertyEqual(expectedTotalResults, result.totalResults); AssertHelper.AssertPropertyEqual(expectedTotalResults, result.totalResults);
@@ -112,7 +113,7 @@ public class GetGroupsListCommandTests
.GetManyByOrganizationIdAsync(organizationId) .GetManyByOrganizationIdAsync(organizationId)
.Returns(groups); .Returns(groups);
var result = await sutProvider.Sut.GetGroupsListAsync(organizationId, filter, null, null); var result = await sutProvider.Sut.GetGroupsListAsync(organizationId, new GetGroupsQueryParamModel { Filter = filter });
AssertHelper.AssertPropertyEqual(expectedGroupList, result.groupList); AssertHelper.AssertPropertyEqual(expectedGroupList, result.groupList);
AssertHelper.AssertPropertyEqual(expectedTotalResults, result.totalResults); AssertHelper.AssertPropertyEqual(expectedTotalResults, result.totalResults);

View File

@@ -1,5 +1,6 @@
using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Scim.Models;
using Bit.Scim.Users; using Bit.Scim.Users;
using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.AutoFixture.Attributes;