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

api adjustments for manager role and collections

This commit is contained in:
Kyle Spearrin 2018-10-17 14:58:45 -04:00
parent ca175e7dd8
commit 7db36e0005
19 changed files with 426 additions and 82 deletions

View File

@ -8,7 +8,7 @@ using Bit.Core.Models.Api;
using Bit.Core.Exceptions;
using Bit.Core.Services;
using Bit.Core;
using Bit.Core.Models.Data;
using Bit.Core.Models.Table;
namespace Bit.Api.Controllers
{
@ -36,25 +36,39 @@ namespace Bit.Api.Controllers
[HttpGet("{id}")]
public async Task<CollectionResponseModel> Get(string orgId, string id)
{
var collection = await _collectionRepository.GetByIdAsync(new Guid(id));
if(collection == null || !_currentContext.OrganizationAdmin(collection.OrganizationId))
{
throw new NotFoundException();
}
var collection = await GetCollectionAsync(new Guid(id), new Guid(orgId));
return new CollectionResponseModel(collection);
}
[HttpGet("{id}/details")]
public async Task<CollectionGroupDetailsResponseModel> GetDetails(string orgId, string id)
{
var collectionDetails = await _collectionRepository.GetByIdWithGroupsAsync(new Guid(id));
if(collectionDetails?.Item1 == null || !_currentContext.OrganizationAdmin(collectionDetails.Item1.OrganizationId))
var orgIdGuid = new Guid(orgId);
if(!_currentContext.OrganizationManager(orgIdGuid))
{
throw new NotFoundException();
}
return new CollectionGroupDetailsResponseModel(collectionDetails.Item1, collectionDetails.Item2);
var idGuid = new Guid(id);
if(_currentContext.OrganizationAdmin(orgIdGuid))
{
var collectionDetails = await _collectionRepository.GetByIdWithGroupsAsync(idGuid);
if(collectionDetails?.Item1 == null || collectionDetails.Item1.OrganizationId != orgIdGuid)
{
throw new NotFoundException();
}
return new CollectionGroupDetailsResponseModel(collectionDetails.Item1, collectionDetails.Item2);
}
else
{
var collectionDetails = await _collectionRepository.GetByIdWithGroupsAsync(idGuid,
_currentContext.UserId.Value);
if(collectionDetails?.Item1 == null || collectionDetails.Item1.OrganizationId != orgIdGuid)
{
throw new NotFoundException();
}
return new CollectionGroupDetailsResponseModel(collectionDetails.Item1, collectionDetails.Item2);
}
}
[HttpGet("")]
@ -72,17 +86,10 @@ namespace Bit.Api.Controllers
}
[HttpGet("~/collections")]
public async Task<ListResponseModel<CollectionDetailsResponseModel>> GetUser([FromQuery]bool writeOnly = false)
public async Task<ListResponseModel<CollectionDetailsResponseModel>> GetUser()
{
var collections = await _collectionRepository.GetManyByUserIdAsync(
_userService.GetProperUserId(User).Value);
// TODO: Deprecated. writeOnly flag can be removed after v1.21.0
if(writeOnly)
{
collections = collections.Where(c => !c.ReadOnly).ToList();
}
var responses = collections.Select(c => new CollectionDetailsResponseModel(c));
return new ListResponseModel<CollectionDetailsResponseModel>(responses);
}
@ -90,13 +97,7 @@ namespace Bit.Api.Controllers
[HttpGet("{id}/users")]
public async Task<ListResponseModel<CollectionUserResponseModel>> GetUsers(string orgId, string id)
{
var idGuid = new Guid(id);
var collection = await _collectionRepository.GetByIdAsync(idGuid);
if(collection == null || !_currentContext.OrganizationAdmin(collection.OrganizationId))
{
throw new NotFoundException();
}
var collection = await GetCollectionAsync(new Guid(id), new Guid(orgId));
var collectionUsers = await _collectionRepository.GetManyUserDetailsByIdAsync(collection.OrganizationId,
collection.Id);
var responses = collectionUsers.Select(c => new CollectionUserResponseModel(c));
@ -107,13 +108,14 @@ namespace Bit.Api.Controllers
public async Task<CollectionResponseModel> Post(string orgId, [FromBody]CollectionRequestModel model)
{
var orgIdGuid = new Guid(orgId);
if(!_currentContext.OrganizationAdmin(orgIdGuid))
if(!_currentContext.OrganizationManager(orgIdGuid))
{
throw new NotFoundException();
}
var collection = model.ToCollection(orgIdGuid);
await _collectionService.SaveAsync(collection, model.Groups?.Select(g => g.ToSelectionReadOnly()));
await _collectionService.SaveAsync(collection, model.Groups?.Select(g => g.ToSelectionReadOnly()),
!_currentContext.OrganizationAdmin(orgIdGuid) ? _currentContext.UserId : null);
return new CollectionResponseModel(collection);
}
@ -121,12 +123,7 @@ namespace Bit.Api.Controllers
[HttpPost("{id}")]
public async Task<CollectionResponseModel> Put(string orgId, string id, [FromBody]CollectionRequestModel model)
{
var collection = await _collectionRepository.GetByIdAsync(new Guid(id));
if(collection == null || !_currentContext.OrganizationAdmin(collection.OrganizationId))
{
throw new NotFoundException();
}
var collection = await GetCollectionAsync(new Guid(id), new Guid(orgId));
await _collectionService.SaveAsync(model.ToCollection(collection),
model.Groups?.Select(g => g.ToSelectionReadOnly()));
return new CollectionResponseModel(collection);
@ -136,12 +133,7 @@ namespace Bit.Api.Controllers
[HttpPost("{id}/delete")]
public async Task Delete(string orgId, string id)
{
var collection = await _collectionRepository.GetByIdAsync(new Guid(id));
if(collection == null || !_currentContext.OrganizationAdmin(collection.OrganizationId))
{
throw new NotFoundException();
}
var collection = await GetCollectionAsync(new Guid(id), new Guid(orgId));
await _collectionService.DeleteAsync(collection);
}
@ -149,13 +141,26 @@ namespace Bit.Api.Controllers
[HttpPost("{id}/delete-user/{orgUserId}")]
public async Task Delete(string orgId, string id, string orgUserId)
{
var collection = await _collectionRepository.GetByIdAsync(new Guid(id));
if(collection == null || !_currentContext.OrganizationAdmin(collection.OrganizationId))
var collection = await GetCollectionAsync(new Guid(id), new Guid(orgId));
await _collectionService.DeleteUserAsync(collection, new Guid(orgUserId));
}
private async Task<Collection> GetCollectionAsync(Guid id, Guid orgId)
{
if(!_currentContext.OrganizationManager(orgId))
{
throw new NotFoundException();
}
await _collectionService.DeleteUserAsync(collection, new Guid(orgUserId));
var collection = _currentContext.OrganizationAdmin(orgId) ?
await _collectionRepository.GetByIdAsync(id) :
await _collectionRepository.GetByIdAsync(id, _currentContext.UserId.Value);
if(collection == null || collection.OrganizationId != orgId)
{
throw new NotFoundException();
}
return collection;
}
}
}

View File

@ -57,7 +57,7 @@ namespace Bit.Api.Controllers
public async Task<ListResponseModel<GroupResponseModel>> Get(string orgId)
{
var orgIdGuid = new Guid(orgId);
if(!_currentContext.OrganizationAdmin(orgIdGuid))
if(!_currentContext.OrganizationManager(orgIdGuid))
{
throw new NotFoundException();
}

View File

@ -58,7 +58,7 @@ namespace Bit.Api.Controllers
public async Task<ListResponseModel<OrganizationUserUserDetailsResponseModel>> Get(string orgId)
{
var orgGuidId = new Guid(orgId);
if(!_currentContext.OrganizationAdmin(orgGuidId))
if(!_currentContext.OrganizationManager(orgGuidId))
{
throw new NotFoundException();
}

View File

@ -111,6 +111,16 @@ namespace Bit.Core
Type = OrganizationUserType.User
}));
}
if(claimsDict.ContainsKey("orgmanager"))
{
Organizations.AddRange(claimsDict["orgmanager"].Select(c =>
new CurrentContentOrganization
{
Id = new Guid(c.Value),
Type = OrganizationUserType.Manager
}));
}
}
public bool OrganizationUser(Guid orgId)
@ -118,6 +128,13 @@ namespace Bit.Core
return Organizations?.Any(o => o.Id == orgId) ?? false;
}
public bool OrganizationManager(Guid orgId)
{
return Organizations?.Any(o => o.Id == orgId &&
(o.Type == OrganizationUserType.Owner || o.Type == OrganizationUserType.Admin ||
o.Type == OrganizationUserType.Manager)) ?? false;
}
public bool OrganizationAdmin(Guid orgId)
{
return Organizations?.Any(o => o.Id == orgId &&

View File

@ -4,6 +4,7 @@
{
Owner = 0,
Admin = 1,
User = 2
User = 2,
Manager = 3,
}
}

View File

@ -19,6 +19,7 @@ namespace Bit.Core.IdentityServer
"device",
"orgowner",
"orgadmin",
"orgmanager",
"orguser"
}),
new ApiResource("internal", new string[] { JwtClaimTypes.Subject }),

View File

@ -75,6 +75,12 @@ namespace Bit.Core.IdentityServer
newClaims.Add(new Claim("orgadmin", org.Id.ToString()));
}
break;
case Enums.OrganizationUserType.Manager:
foreach(var org in group)
{
newClaims.Add(new Claim("orgmanager", org.Id.ToString()));
}
break;
case Enums.OrganizationUserType.User:
foreach(var org in group)
{

View File

@ -10,11 +10,14 @@ namespace Bit.Core.Repositories
{
Task<int> GetCountByOrganizationIdAsync(Guid organizationId);
Task<Tuple<Collection, ICollection<SelectionReadOnly>>> GetByIdWithGroupsAsync(Guid id);
Task<Tuple<CollectionDetails, ICollection<SelectionReadOnly>>> GetByIdWithGroupsAsync(Guid id, Guid userId);
Task<ICollection<Collection>> GetManyByOrganizationIdAsync(Guid organizationId);
Task<CollectionDetails> GetByIdAsync(Guid id, Guid userId);
Task<ICollection<CollectionDetails>> GetManyByUserIdAsync(Guid userId);
Task<ICollection<CollectionUserDetails>> GetManyUserDetailsByIdAsync(Guid organizationId, Guid collectionId);
Task CreateAsync(Collection obj, IEnumerable<SelectionReadOnly> groups);
Task ReplaceAsync(Collection obj, IEnumerable<SelectionReadOnly> groups);
Task DeleteUserAsync(Guid collectionId, Guid organizationUserId);
Task UpdateUsersAsync(Guid id, IEnumerable<SelectionReadOnly> users);
}
}

View File

@ -51,6 +51,23 @@ namespace Bit.Core.Repositories.SqlServer
}
}
public async Task<Tuple<CollectionDetails, ICollection<SelectionReadOnly>>> GetByIdWithGroupsAsync(
Guid id, Guid userId)
{
using(var connection = new SqlConnection(ConnectionString))
{
var results = await connection.QueryMultipleAsync(
$"[{Schema}].[Collection_ReadWithGroupsByIdUserId]",
new { Id = id, UserId = userId },
commandType: CommandType.StoredProcedure);
var collection = await results.ReadFirstOrDefaultAsync<CollectionDetails>();
var groups = (await results.ReadAsync<SelectionReadOnly>()).ToList();
return new Tuple<CollectionDetails, ICollection<SelectionReadOnly>>(collection, groups);
}
}
public async Task<ICollection<Collection>> GetManyByOrganizationIdAsync(Guid organizationId)
{
using(var connection = new SqlConnection(ConnectionString))
@ -64,6 +81,19 @@ namespace Bit.Core.Repositories.SqlServer
}
}
public async Task<CollectionDetails> GetByIdAsync(Guid id, Guid userId)
{
using(var connection = new SqlConnection(ConnectionString))
{
var results = await connection.QueryAsync<CollectionDetails>(
$"[{Schema}].[Collection_ReadByIdUserId]",
new { Id = id, UserId = userId },
commandType: CommandType.StoredProcedure);
return results.FirstOrDefault();
}
}
public async Task<ICollection<CollectionDetails>> GetManyByUserIdAsync(Guid userId)
{
using(var connection = new SqlConnection(ConnectionString))
@ -108,7 +138,7 @@ namespace Bit.Core.Repositories.SqlServer
using(var connection = new SqlConnection(ConnectionString))
{
var results = await connection.ExecuteAsync(
$"[{Schema}].[Collection_CreateWithGroups]",
$"[{Schema}].[Collection_CreateWithGroupsAndUsers]",
objWithGroups,
commandType: CommandType.StoredProcedure);
}
@ -128,6 +158,17 @@ namespace Bit.Core.Repositories.SqlServer
}
}
public async Task CreateUserAsync(Guid collectionId, Guid organizationUserId)
{
using(var connection = new SqlConnection(ConnectionString))
{
var results = await connection.ExecuteAsync(
$"[{Schema}].[CollectionUser_Create]",
new { CollectionId = collectionId, OrganizationUserId = organizationUserId },
commandType: CommandType.StoredProcedure);
}
}
public async Task DeleteUserAsync(Guid collectionId, Guid organizationUserId)
{
using(var connection = new SqlConnection(ConnectionString))
@ -139,6 +180,17 @@ namespace Bit.Core.Repositories.SqlServer
}
}
public async Task UpdateUsersAsync(Guid id, IEnumerable<SelectionReadOnly> users)
{
using(var connection = new SqlConnection(ConnectionString))
{
var results = await connection.ExecuteAsync(
$"[{Schema}].[Collection_UpdateUsers]",
new { Id = id, Users = users.ToArrayTVP() },
commandType: CommandType.StoredProcedure);
}
}
public class CollectionWithGroups : Collection
{
public DataTable Groups { get; set; }

View File

@ -8,7 +8,7 @@ namespace Bit.Core.Services
{
public interface ICollectionService
{
Task SaveAsync(Collection collection, IEnumerable<SelectionReadOnly> groups = null);
Task SaveAsync(Collection collection, IEnumerable<SelectionReadOnly> groups = null, Guid? assignUserId = null);
Task DeleteAsync(Collection collection);
Task DeleteUserAsync(Collection collection, Guid organizationUserId);
}

View File

@ -33,7 +33,8 @@ namespace Bit.Core.Services
_mailService = mailService;
}
public async Task SaveAsync(Collection collection, IEnumerable<SelectionReadOnly> groups = null)
public async Task SaveAsync(Collection collection, IEnumerable<SelectionReadOnly> groups = null,
Guid? assignUserId = null)
{
var org = await _organizationRepository.GetByIdAsync(collection.OrganizationId);
if(org == null)
@ -62,6 +63,18 @@ namespace Bit.Core.Services
await _collectionRepository.CreateAsync(collection, groups);
}
// Assign a user to the newly created collection.
if(assignUserId.HasValue)
{
var orgUser = await _organizationUserRepository.GetByOrganizationAsync(org.Id, assignUserId.Value);
if(orgUser != null && orgUser.Status == Enums.OrganizationUserStatusType.Confirmed)
{
await _collectionRepository.UpdateUsersAsync(collection.Id,
new List<SelectionReadOnly> {
new SelectionReadOnly { Id = orgUser.Id, ReadOnly = false } });
}
}
await _eventService.LogCollectionEventAsync(collection, Enums.EventType.Collection_Created);
}
else

View File

@ -153,7 +153,6 @@
<Build Include="dbo\Stored Procedures\Device_ReadByUserId.sql" />
<Build Include="dbo\Stored Procedures\Device_Update.sql" />
<Build Include="dbo\Stored Procedures\User_Create.sql" />
<Build Include="dbo\Stored Procedures\Collection_CreateWithGroups.sql" />
<Build Include="dbo\Stored Procedures\User_DeleteById.sql" />
<Build Include="dbo\Stored Procedures\Collection_ReadWithGroupsById.sql" />
<Build Include="dbo\Stored Procedures\User_ReadByEmail.sql" />
@ -233,5 +232,10 @@
<Build Include="dbo\Stored Procedures\User_ReadKdfByEmail.sql" />
<Build Include="dbo\Stored Procedures\Cipher_DeleteByOrganizationId.sql" />
<Build Include="dbo\Stored Procedures\AzureSQLMaintenance.sql" />
<Build Include="dbo\Functions\UserCollectionDetails.sql" />
<Build Include="dbo\Stored Procedures\Collection_ReadByIdUserId.sql" />
<Build Include="dbo\Stored Procedures\Collection_ReadWithGroupsByIdUserId.sql" />
<Build Include="dbo\Stored Procedures\Collection_CreateWithGroups.sql" />
<Build Include="dbo\Stored Procedures\Collection_UpdateUsers.sql" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,38 @@
CREATE FUNCTION [dbo].[UserCollectionDetails](@UserId UNIQUEIDENTIFIER)
RETURNS TABLE
AS RETURN
SELECT
C.*,
CASE
WHEN
OU.[AccessAll] = 1
OR G.[AccessAll] = 1
OR CU.[ReadOnly] = 0
OR CG.[ReadOnly] = 0
THEN 0
ELSE 1
END [ReadOnly]
FROM
[dbo].[CollectionView] C
INNER JOIN
[dbo].[OrganizationUser] OU ON C.[OrganizationId] = OU.[OrganizationId]
INNER JOIN
[dbo].[Organization] O ON O.[Id] = C.[OrganizationId]
LEFT JOIN
[dbo].[CollectionUser] CU ON OU.[AccessAll] = 0 AND CU.[CollectionId] = C.[Id] AND CU.[OrganizationUserId] = [OU].[Id]
LEFT JOIN
[dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND OU.[AccessAll] = 0 AND GU.[OrganizationUserId] = OU.[Id]
LEFT JOIN
[dbo].[Group] G ON G.[Id] = GU.[GroupId]
LEFT JOIN
[dbo].[CollectionGroup] CG ON G.[AccessAll] = 0 AND CG.[CollectionId] = C.[Id] AND CG.[GroupId] = GU.[GroupId]
WHERE
OU.[UserId] = @UserId
AND OU.[Status] = 2 -- 2 = Confirmed
AND O.[Enabled] = 1
AND (
OU.[AccessAll] = 1
OR CU.[CollectionId] IS NOT NULL
OR G.[AccessAll] = 1
OR CG.[CollectionId] IS NOT NULL
)

View File

@ -0,0 +1,15 @@
CREATE PROCEDURE [dbo].[Collection_ReadByIdUserId]
@Id UNIQUEIDENTIFIER,
@UserId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
SELECT TOP 1
*
FROM
[dbo].[UserCollectionDetails](@UserId)
WHERE
[Id] = @Id
ORDER BY
[ReadOnly] ASC
END

View File

@ -5,38 +5,7 @@ BEGIN
SET NOCOUNT ON
SELECT
C.*,
CASE
WHEN
OU.[AccessAll] = 1
OR G.[AccessAll] = 1
OR CU.[ReadOnly] = 0
OR CG.[ReadOnly] = 0
THEN 0
ELSE 1
END [ReadOnly]
*
FROM
[dbo].[CollectionView] C
INNER JOIN
[dbo].[OrganizationUser] OU ON C.[OrganizationId] = OU.[OrganizationId]
INNER JOIN
[dbo].[Organization] O ON O.[Id] = C.[OrganizationId]
LEFT JOIN
[dbo].[CollectionUser] CU ON OU.[AccessAll] = 0 AND CU.[CollectionId] = C.[Id] AND CU.[OrganizationUserId] = [OU].[Id]
LEFT JOIN
[dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND OU.[AccessAll] = 0 AND GU.[OrganizationUserId] = OU.[Id]
LEFT JOIN
[dbo].[Group] G ON G.[Id] = GU.[GroupId]
LEFT JOIN
[dbo].[CollectionGroup] CG ON G.[AccessAll] = 0 AND CG.[CollectionId] = C.[Id] AND CG.[GroupId] = GU.[GroupId]
WHERE
OU.[UserId] = @UserId
AND OU.[Status] = 2 -- 2 = Confirmed
AND O.[Enabled] = 1
AND (
OU.[AccessAll] = 1
OR CU.[CollectionId] IS NOT NULL
OR G.[AccessAll] = 1
OR CG.[CollectionId] IS NOT NULL
)
[dbo].[UserCollectionDetails](@UserId)
END

View File

@ -0,0 +1,17 @@
CREATE PROCEDURE [dbo].[Collection_ReadWithGroupsByIdUserId]
@Id UNIQUEIDENTIFIER,
@UserId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
EXEC [dbo].[Collection_ReadByIdUserId] @Id, @UserId
SELECT
[GroupId] [Id],
[ReadOnly]
FROM
[dbo].[CollectionGroup]
WHERE
[CollectionId] = @Id
END

View File

@ -0,0 +1,41 @@
CREATE PROCEDURE [dbo].[Collection_UpdateUsers]
@Id UNIQUEIDENTIFIER,
@Users AS [dbo].[SelectionReadOnlyArray] READONLY
AS
BEGIN
SET NOCOUNT ON
DECLARE @OrganizationId UNIQUEIDENTIFIER = (SELECT [OrganizationId] FROM [dbo].[Collection] WHERE [Id] = @Id)
;WITH [AvailableUsersCTE] AS(
SELECT
Id
FROM
[dbo].[OrganizationUser]
WHERE
OrganizationId = @OrganizationId
)
MERGE
[dbo].[CollectionUser] AS [Target]
USING
@Users AS [Source]
ON
[Target].[CollectionId] = @Id
AND [Target].[OrganizationUserId] = [Source].[Id]
WHEN NOT MATCHED BY TARGET
AND [Source].[Id] IN (SELECT [Id] FROM [AvailableUsersCTE]) THEN
INSERT VALUES
(
@Id,
[Source].[Id],
[Source].[ReadOnly]
)
WHEN MATCHED AND [Target].[ReadOnly] != [Source].[ReadOnly] THEN
UPDATE SET [Target].[ReadOnly] = [Source].[ReadOnly]
WHEN NOT MATCHED BY SOURCE
AND [Target].[CollectionId] = @Id THEN
DELETE
;
EXEC [dbo].[User_BumpAccountRevisionDateByCollectionId] @Id, @OrganizationId
END

View File

@ -0,0 +1,161 @@
IF OBJECT_ID('[dbo].[UserCollectionDetails]') IS NOT NULL
BEGIN
DROP FUNCTION [dbo].[UserCollectionDetails]
END
GO
CREATE FUNCTION [dbo].[UserCollectionDetails](@UserId UNIQUEIDENTIFIER)
RETURNS TABLE
AS RETURN
SELECT
C.*,
CASE
WHEN
OU.[AccessAll] = 1
OR G.[AccessAll] = 1
OR CU.[ReadOnly] = 0
OR CG.[ReadOnly] = 0
THEN 0
ELSE 1
END [ReadOnly]
FROM
[dbo].[CollectionView] C
INNER JOIN
[dbo].[OrganizationUser] OU ON C.[OrganizationId] = OU.[OrganizationId]
INNER JOIN
[dbo].[Organization] O ON O.[Id] = C.[OrganizationId]
LEFT JOIN
[dbo].[CollectionUser] CU ON OU.[AccessAll] = 0 AND CU.[CollectionId] = C.[Id] AND CU.[OrganizationUserId] = [OU].[Id]
LEFT JOIN
[dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND OU.[AccessAll] = 0 AND GU.[OrganizationUserId] = OU.[Id]
LEFT JOIN
[dbo].[Group] G ON G.[Id] = GU.[GroupId]
LEFT JOIN
[dbo].[CollectionGroup] CG ON G.[AccessAll] = 0 AND CG.[CollectionId] = C.[Id] AND CG.[GroupId] = GU.[GroupId]
WHERE
OU.[UserId] = @UserId
AND OU.[Status] = 2 -- 2 = Confirmed
AND O.[Enabled] = 1
AND (
OU.[AccessAll] = 1
OR CU.[CollectionId] IS NOT NULL
OR G.[AccessAll] = 1
OR CG.[CollectionId] IS NOT NULL
)
GO
IF OBJECT_ID('[dbo].[Collection_ReadByIdUserId]') IS NOT NULL
BEGIN
DROP PROCEDURE [dbo].[Collection_ReadByIdUserId]
END
GO
CREATE PROCEDURE [dbo].[Collection_ReadByIdUserId]
@Id UNIQUEIDENTIFIER,
@UserId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
SELECT TOP 1
*
FROM
[dbo].[UserCollectionDetails](@UserId)
WHERE
[Id] = @Id
ORDER BY
[ReadOnly] ASC
END
GO
IF OBJECT_ID('[dbo].[Collection_ReadByUserId]') IS NOT NULL
BEGIN
DROP PROCEDURE [dbo].[Collection_ReadByUserId]
END
GO
CREATE PROCEDURE [dbo].[Collection_ReadByUserId]
@UserId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
SELECT
*
FROM
[dbo].[UserCollectionDetails](@UserId)
END
GO
IF OBJECT_ID('[dbo].[Collection_ReadWithGroupsByIdUserId]') IS NOT NULL
BEGIN
DROP PROCEDURE [dbo].[Collection_ReadWithGroupsByIdUserId]
END
GO
CREATE PROCEDURE [dbo].[Collection_ReadWithGroupsByIdUserId]
@Id UNIQUEIDENTIFIER,
@UserId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
EXEC [dbo].[Collection_ReadByIdUserId] @Id, @UserId
SELECT
[GroupId] [Id],
[ReadOnly]
FROM
[dbo].[CollectionGroup]
WHERE
[CollectionId] = @Id
END
GO
IF OBJECT_ID('[dbo].[Collection_UpdateUsers]') IS NOT NULL
BEGIN
DROP PROCEDURE [dbo].[Collection_UpdateUsers]
END
GO
CREATE PROCEDURE [dbo].[Collection_UpdateUsers]
@Id UNIQUEIDENTIFIER,
@Users AS [dbo].[SelectionReadOnlyArray] READONLY
AS
BEGIN
SET NOCOUNT ON
DECLARE @OrganizationId UNIQUEIDENTIFIER = (SELECT [OrganizationId] FROM [dbo].[Collection] WHERE [Id] = @Id)
;WITH [AvailableUsersCTE] AS(
SELECT
Id
FROM
[dbo].[OrganizationUser]
WHERE
OrganizationId = @OrganizationId
)
MERGE
[dbo].[CollectionUser] AS [Target]
USING
@Users AS [Source]
ON
[Target].[CollectionId] = @Id
AND [Target].[OrganizationUserId] = [Source].[Id]
WHEN NOT MATCHED BY TARGET
AND [Source].[Id] IN (SELECT [Id] FROM [AvailableUsersCTE]) THEN
INSERT VALUES
(
@Id,
[Source].[Id],
[Source].[ReadOnly]
)
WHEN MATCHED AND [Target].[ReadOnly] != [Source].[ReadOnly] THEN
UPDATE SET [Target].[ReadOnly] = [Source].[ReadOnly]
WHEN NOT MATCHED BY SOURCE
AND [Target].[CollectionId] = @Id THEN
DELETE
;
EXEC [dbo].[User_BumpAccountRevisionDateByCollectionId] @Id, @OrganizationId
END
GO

View File

@ -14,6 +14,7 @@
<ItemGroup>
<None Remove="DbScripts\2018-09-25_00_OrgPurge.sql" />
<None Remove="DbScripts\2018-10-17_00_ManagerRole.sql" />
</ItemGroup>
<ItemGroup>