1
0
mirror of https://github.com/bitwarden/server.git synced 2025-02-22 02:51:33 +01:00

group user assignment apis

This commit is contained in:
Kyle Spearrin 2017-05-09 19:04:01 -04:00
parent 07878cbaeb
commit 7a4d20ac1f
15 changed files with 249 additions and 3 deletions

View File

@ -8,6 +8,7 @@ using Bit.Core.Models.Api;
using Bit.Core.Exceptions;
using Bit.Core.Services;
using Bit.Core;
using System.Collections.Generic;
namespace Bit.Api.Controllers
{
@ -67,6 +68,21 @@ namespace Bit.Api.Controllers
return new ListResponseModel<GroupResponseModel>(responses);
}
[HttpGet("{id}/users")]
public async Task<ListResponseModel<GroupUserResponseModel>> GetUsers(string orgId, string id)
{
var idGuid = new Guid(id);
var group = await _groupRepository.GetByIdAsync(idGuid);
if(group == null || !_currentContext.OrganizationAdmin(group.OrganizationId))
{
throw new NotFoundException();
}
var groups = await _groupRepository.GetManyUserDetailsByIdAsync(idGuid);
var responses = groups.Select(g => new GroupUserResponseModel(g));
return new ListResponseModel<GroupUserResponseModel>(responses);
}
[HttpPost("")]
public async Task<GroupResponseModel> Post(string orgId, [FromBody]GroupRequestModel model)
{

View File

@ -8,6 +8,7 @@ using Bit.Core.Models.Api;
using Bit.Core.Exceptions;
using Bit.Core.Services;
using Bit.Core;
using System.Collections.Generic;
namespace Bit.Api.Controllers
{
@ -19,6 +20,7 @@ namespace Bit.Api.Controllers
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IOrganizationService _organizationService;
private readonly ICollectionRepository _collectionRepository;
private readonly IGroupRepository _groupRepository;
private readonly IUserService _userService;
private readonly CurrentContext _currentContext;
@ -27,6 +29,7 @@ namespace Bit.Api.Controllers
IOrganizationUserRepository organizationUserRepository,
IOrganizationService organizationService,
ICollectionRepository collectionRepository,
IGroupRepository groupRepository,
IUserService userService,
CurrentContext currentContext)
{
@ -34,6 +37,7 @@ namespace Bit.Api.Controllers
_organizationUserRepository = organizationUserRepository;
_organizationService = organizationService;
_collectionRepository = collectionRepository;
_groupRepository = groupRepository;
_userService = userService;
_currentContext = currentContext;
}
@ -64,6 +68,20 @@ namespace Bit.Api.Controllers
return new ListResponseModel<OrganizationUserResponseModel>(responses);
}
[HttpGet("{id}/groups")]
public async Task<IEnumerable<string>> GetGroups(string orgId, string id)
{
var organizationUser = await _organizationUserRepository.GetByIdAsync(new Guid(id));
if(organizationUser == null || !_currentContext.OrganizationAdmin(organizationUser.OrganizationId))
{
throw new NotFoundException();
}
var groupIds = await _groupRepository.GetManyIdsByUserIdAsync(organizationUser.Id);
var responses = groupIds.Select(g => g.ToString());
return responses;
}
[HttpPost("invite")]
public async Task Invite(string orgId, [FromBody]OrganizationUserInviteRequestModel model)
{
@ -135,6 +153,25 @@ namespace Bit.Api.Controllers
model.Collections?.Select(c => c.ToCollectionUser()));
}
[HttpPut("{id}/groups")]
[HttpPost("{id}/groups")]
public async Task PutGroups(string orgId, string id, [FromBody]OrganizationUserUpdateGroupsRequestModel model)
{
var orgGuidId = new Guid(orgId);
if(!_currentContext.OrganizationAdmin(orgGuidId))
{
throw new NotFoundException();
}
var organizationUser = await _organizationUserRepository.GetByIdAsync(new Guid(id));
if(organizationUser == null || organizationUser.OrganizationId != orgGuidId)
{
throw new NotFoundException();
}
await _organizationUserRepository.UpdateGroupsAsync(organizationUser.Id, model.GroupIds.Select(g => new Guid(g)));
}
[HttpDelete("{id}")]
[HttpPost("{id}/delete")]
public async Task Delete(string orgId, string id)

View File

@ -43,6 +43,12 @@ namespace Bit.Core.Models.Api
}
}
public class OrganizationUserUpdateGroupsRequestModel
{
[Required]
public IEnumerable<string> GroupIds { get; set; }
}
public class OrganizationUserCollectionRequestModel
{
[Required]

View File

@ -0,0 +1,34 @@
using System;
using Bit.Core.Models.Data;
using Bit.Core.Enums;
namespace Bit.Core.Models.Api
{
public class GroupUserResponseModel : ResponseModel
{
public GroupUserResponseModel(GroupUserUserDetails groupUser)
: base("groupUser")
{
if(groupUser == null)
{
throw new ArgumentNullException(nameof(groupUser));
}
OrganizationUserId = groupUser.OrganizationUserId.ToString();
GroupId = groupUser.GroupId.ToString();
AccessAll = groupUser.AccessAll;
Name = groupUser.Name;
Email = groupUser.Email;
Type = groupUser.Type;
Status = groupUser.Status;
}
public string OrganizationUserId { get; set; }
public string GroupId { get; set; }
public bool AccessAll { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public OrganizationUserType Type { get; set; }
public OrganizationUserStatusType Status { get; set; }
}
}

View File

@ -0,0 +1,16 @@
using System;
namespace Bit.Core.Models.Data
{
public class GroupUserUserDetails
{
public Guid OrganizationUserId { get; set; }
public Guid OrganizationId { get; set; }
public Guid GroupId { get; set; }
public bool AccessAll { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public Enums.OrganizationUserStatusType Status { get; set; }
public Enums.OrganizationUserType Type { get; set; }
}
}

View File

@ -2,6 +2,7 @@
using Bit.Core.Models.Table;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.Core.Models.Data;
namespace Bit.Core.Repositories
{
@ -9,6 +10,8 @@ namespace Bit.Core.Repositories
{
Task<Tuple<Group, ICollection<Guid>>> GetByIdWithCollectionsAsync(Guid id);
Task<ICollection<Group>> GetManyByOrganizationIdAsync(Guid organizationId);
Task<ICollection<GroupUserUserDetails>> GetManyUserDetailsByIdAsync(Guid id);
Task<ICollection<Guid>> GetManyIdsByUserIdAsync(Guid organizationUserId);
Task CreateAsync(Group obj, IEnumerable<Guid> collectionIds);
Task ReplaceAsync(Group obj, IEnumerable<Guid> collectionIds);
}

View File

@ -20,5 +20,6 @@ namespace Bit.Core.Repositories
Task<ICollection<OrganizationUserUserDetails>> GetManyDetailsByOrganizationAsync(Guid organizationId);
Task<ICollection<OrganizationUserOrganizationDetails>> GetManyDetailsByUserAsync(Guid userId,
OrganizationUserStatusType? status = null);
Task UpdateGroupsAsync(Guid orgUserId, IEnumerable<Guid> groupIds);
}
}

View File

@ -8,6 +8,7 @@ using System.Data.SqlClient;
using System.Linq;
using Newtonsoft.Json;
using Bit.Core.Utilities;
using Bit.Core.Models.Data;
namespace Bit.Core.Repositories.SqlServer
{
@ -50,6 +51,32 @@ namespace Bit.Core.Repositories.SqlServer
}
}
public async Task<ICollection<GroupUserUserDetails>> GetManyUserDetailsByIdAsync(Guid id)
{
using(var connection = new SqlConnection(ConnectionString))
{
var results = await connection.QueryAsync<GroupUserUserDetails>(
$"[{Schema}].[GroupUserUserDetails_ReadByGroupId]",
new { GroupId = id },
commandType: CommandType.StoredProcedure);
return results.ToList();
}
}
public async Task<ICollection<Guid>> GetManyIdsByUserIdAsync(Guid organizationUserId)
{
using(var connection = new SqlConnection(ConnectionString))
{
var results = await connection.QueryAsync<Guid>(
$"[{Schema}].[GroupUser_ReadGroupIdsByOrganizationUserId]",
new { OrganizationUserId = organizationUserId },
commandType: CommandType.StoredProcedure);
return results.ToList();
}
}
public async Task CreateAsync(Group obj, IEnumerable<Guid> collectionIds)
{
obj.SetNewId();

View File

@ -8,6 +8,7 @@ using System.Linq;
using Bit.Core.Models.Data;
using System.Collections.Generic;
using Bit.Core.Enums;
using Bit.Core.Utilities;
namespace Bit.Core.Repositories.SqlServer
{
@ -155,5 +156,16 @@ namespace Bit.Core.Repositories.SqlServer
return results.ToList();
}
}
public async Task UpdateGroupsAsync(Guid orgUserId, IEnumerable<Guid> groupIds)
{
using(var connection = new SqlConnection(ConnectionString))
{
var results = await connection.ExecuteAsync(
"[dbo].[GroupUser_UpdateGroups]",
new { OrganizationUserId = orgUserId, GroupIds = groupIds.ToGuidIdArrayTVP() },
commandType: CommandType.StoredProcedure);
}
}
}
}

View File

@ -190,5 +190,12 @@
<Build Include="dbo\Stored Procedures\Collection_UpdateWithGroups.sql" />
<Build Include="dbo\Stored Procedures\Collection_CreateWithGroups.sql" />
<Build Include="dbo\Stored Procedures\Collection_ReadWithGroupsById.sql" />
<Build Include="dbo\Views\GroupUserUserDetailsView.sql" />
<Build Include="dbo\Stored Procedures\GroupUserUserDetails_ReadByGroupId.sql" />
<Build Include="dbo\Stored Procedures\GroupUser_ReadGroupIdsByOrganizationUserId.sql" />
<Build Include="dbo\Stored Procedures\GroupUser_UpdateGroups.sql" />
</ItemGroup>
<ItemGroup>
<RefactorLog Include="Sql.refactorlog" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,13 @@
CREATE PROCEDURE [dbo].[GroupUserUserDetails_ReadByGroupId]
@GroupId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
SELECT
*
FROM
[dbo].[GroupUserUserDetailsView]
WHERE
[GroupId] = @GroupId
END

View File

@ -0,0 +1,13 @@
CREATE PROCEDURE [dbo].[GroupUser_ReadGroupIdsByOrganizationUserId]
@OrganizationUserId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
SELECT
[GroupId]
FROM
[dbo].[GroupUser]
WHERE
[OrganizationUserId] = @OrganizationUserId
END

View File

@ -0,0 +1,44 @@
CREATE PROCEDURE [dbo].[GroupUser_UpdateGroups]
@OrganizationUserId UNIQUEIDENTIFIER,
@GroupIds AS [dbo].[GuidIdArray] READONLY
AS
BEGIN
SET NOCOUNT ON
DECLARE @OrgId UNIQUEIDENTIFIER = (
SELECT TOP 1
[OrganizationId]
FROM
[dbo].[OrganizationUser]
WHERE
[Id] = @OrganizationUserId
)
;WITH [AvailableGroupsCTE] AS(
SELECT
[Id]
FROM
[dbo].[Group]
WHERE
[OrganizationId] = @OrgId
)
MERGE
[dbo].[GroupUser] AS [Target]
USING
@GroupIds AS [Source]
ON
[Target].[GroupId] = [Source].[Id]
AND [Target].[OrganizationUserId] = @OrganizationUserId
WHEN NOT MATCHED BY TARGET
AND [Source].[Id] IN (SELECT [Id] FROM [AvailableGroupsCTE]) THEN
INSERT VALUES
(
[Source].[Id],
@OrganizationUserId
)
WHEN NOT MATCHED BY SOURCE
AND [Target].[OrganizationUserId] = @OrganizationUserId
AND [Target].[GroupId] IN (SELECT [Id] FROM [AvailableGroupsCTE]) THEN
DELETE
;
END

View File

@ -1,8 +1,8 @@
CREATE TABLE [dbo].[GroupUser] (
[GroupId] UNIQUEIDENTIFIER NOT NULL,
[UserId] UNIQUEIDENTIFIER NOT NULL,
CONSTRAINT [PK_GroupUser] PRIMARY KEY CLUSTERED ([GroupId] ASC, [UserId] ASC),
[OrganizationUserId] UNIQUEIDENTIFIER NOT NULL,
CONSTRAINT [PK_GroupUser] PRIMARY KEY CLUSTERED ([GroupId] ASC, [OrganizationUserId] ASC),
CONSTRAINT [FK_GroupUser_Group] FOREIGN KEY ([GroupId]) REFERENCES [dbo].[Group] ([Id]) ON DELETE CASCADE,
CONSTRAINT [FK_GroupUser_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id])
CONSTRAINT [FK_GroupUser_OrganizationUser] FOREIGN KEY ([OrganizationUserId]) REFERENCES [dbo].[OrganizationUser] ([Id])
);

View File

@ -0,0 +1,17 @@
CREATE VIEW [dbo].[GroupUserUserDetailsView]
AS
SELECT
OU.[Id] AS [OrganizationUserId],
OU.[OrganizationId],
OU.[AccessAll],
GU.[GroupId],
U.[Name],
ISNULL(U.[Email], OU.[Email]) Email,
OU.[Status],
OU.[Type]
FROM
[dbo].[OrganizationUser] OU
INNER JOIN
[dbo].[GroupUser] GU ON GU.[OrganizationUserId] = OU.[Id]
INNER JOIN
[dbo].[User] U ON U.[Id] = OU.[UserId]