1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-29 13:25:17 +01:00

SubvaultUser APIs and services

This commit is contained in:
Kyle Spearrin 2017-03-09 23:58:43 -05:00
parent 7bff121eeb
commit 4bcfab589d
24 changed files with 287 additions and 44 deletions

View File

@ -58,7 +58,7 @@ namespace Bit.Api.Controllers
var result = await _organizationService.InviteUserAsync(new Guid(orgId), model.Email); var result = await _organizationService.InviteUserAsync(new Guid(orgId), model.Email);
} }
[HttpPut("accept")] [HttpPut("{id}/accept")]
[HttpPost("{id}/accept")] [HttpPost("{id}/accept")]
public async Task Accept(string orgId, string id, [FromBody]OrganizationUserAcceptRequestModel model) public async Task Accept(string orgId, string id, [FromBody]OrganizationUserAcceptRequestModel model)
{ {
@ -66,13 +66,26 @@ namespace Bit.Api.Controllers
var result = await _organizationService.AcceptUserAsync(new Guid(id), user, model.Token); var result = await _organizationService.AcceptUserAsync(new Guid(id), user, model.Token);
} }
[HttpPost("confirm")] [HttpPut("{id}/confirm")]
[HttpPost("{id}/confirm")] [HttpPost("{id}/confirm")]
public async Task Confirm(string orgId, string id, [FromBody]OrganizationUserConfirmRequestModel model) public async Task Confirm(string orgId, string id, [FromBody]OrganizationUserConfirmRequestModel model)
{ {
var result = await _organizationService.ConfirmUserAsync(new Guid(id), model.Key); var result = await _organizationService.ConfirmUserAsync(new Guid(id), model.Key);
} }
[HttpPut("{id}")]
[HttpPost("{id}")]
public async Task Put(string id, [FromBody]OrganizationUserUpdateRequestModel model)
{
var organizationUser = await _organizationUserRepository.GetByIdAsync(new Guid(id));
if(organizationUser == null)
{
throw new NotFoundException();
}
await _organizationService.SaveUserAsync(organizationUser, model.Subvaults.Select(s => s.ToSubvaultUser()));
}
[HttpDelete("{id}")] [HttpDelete("{id}")]
[HttpPost("{id}/delete")] [HttpPost("{id}/delete")]
public async Task Delete(string orgId, string id) public async Task Delete(string orgId, string id)

View File

@ -79,6 +79,20 @@ namespace Bit.Api.Controllers
return new SubvaultResponseModel(subvault); return new SubvaultResponseModel(subvault);
} }
[HttpPut("user")]
[HttpPost("user")]
public async Task PutUserSubvaults(string orgId, [FromBody]OrganizationUserConfirmRequestModel model)
{
}
[HttpPut("{id}/users")]
[HttpPost("{id}/users")]
public async Task PutUsers(string orgId, string id, [FromBody]SubvaultRequestModel model)
{
}
[HttpDelete("{id}")] [HttpDelete("{id}")]
[HttpPost("{id}/delete")] [HttpPost("{id}/delete")]
public async Task Delete(string orgId, string id) public async Task Delete(string orgId, string id)

View File

@ -1,19 +0,0 @@
using Bit.Core.Models.Table;
namespace Bit.Core.Models.Api
{
public class OrganizationUserInviteRequestModel
{
public string Email { get; set; }
}
public class OrganizationUserAcceptRequestModel
{
public string Token { get; set; }
}
public class OrganizationUserConfirmRequestModel
{
public string Key { get; set; }
}
}

View File

@ -0,0 +1,52 @@
using Bit.Core.Models.Table;
using System;
using System.Collections.Generic;
namespace Bit.Core.Models.Api
{
public class OrganizationUserInviteRequestModel
{
public string Email { get; set; }
}
public class OrganizationUserAcceptRequestModel
{
public string Token { get; set; }
}
public class OrganizationUserConfirmRequestModel
{
public string Key { get; set; }
}
public class OrganizationUserUpdateRequestModel
{
public Enums.OrganizationUserType Type { get; set; }
public IEnumerable<Subvault> Subvaults { get; set; }
public class Subvault
{
public string Id { get; set; }
public string SubvaultId { get; set; }
public bool Admin { get; set; }
public bool ReadOnly { get; set; }
public SubvaultUser ToSubvaultUser()
{
var user = new SubvaultUser
{
SubvaultId = new Guid(SubvaultId),
Admin = Admin,
ReadOnly = ReadOnly
};
if(string.IsNullOrWhiteSpace(Id))
{
user.Id = new Guid(Id);
}
return user;
}
}
}
}

View File

@ -0,0 +1,42 @@
using System;
using System.ComponentModel.DataAnnotations;
using Bit.Core.Utilities;
using Bit.Core.Models.Table;
using Newtonsoft.Json;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Bit.Core.Models.Api
{
public class SubvaultUserSubvaultRequestModel
{
public string UserId { get; set; }
public IEnumerable<Subvault> Subvaults { get; set; }
public IEnumerable<SubvaultUser> ToSubvaultUsers()
{
return Subvaults.Select(s => new SubvaultUser
{
OrganizationUserId = new Guid(UserId),
SubvaultId = new Guid(s.SubvaultId),
Admin = s.Admin,
ReadOnly = s.ReadOnly
});
}
public class Subvault
{
public string SubvaultId { get; set; }
public bool Admin { get; set; }
public bool ReadOnly { get; set; }
}
}
public class SubvaultUserUserRequestModel
{
public string UserId { get; set; }
public bool Admin { get; set; }
public bool ReadOnly { get; set; }
}
}

View File

@ -7,7 +7,7 @@ namespace Bit.Core.Models.Table
{ {
public Guid Id { get; set; } public Guid Id { get; set; }
public Guid SubvaultId { get; set; } public Guid SubvaultId { get; set; }
public Guid UserId { get; set; } public Guid OrganizationUserId { get; set; }
public bool Admin { get; set; } public bool Admin { get; set; }
public bool ReadOnly { get; set; } public bool ReadOnly { get; set; }
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow; public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;

View File

@ -9,6 +9,7 @@ namespace Bit.Core.Repositories
{ {
Task<Subvault> GetByIdAdminUserIdAsync(Guid id, Guid userId); Task<Subvault> GetByIdAdminUserIdAsync(Guid id, Guid userId);
Task<ICollection<Subvault>> GetManyByOrganizationIdAdminUserIdAsync(Guid organizationId, Guid userId); Task<ICollection<Subvault>> GetManyByOrganizationIdAdminUserIdAsync(Guid organizationId, Guid userId);
Task<ICollection<Subvault>> GetManyByOrganizationIdAsync(Guid organizationId);
Task<ICollection<Subvault>> GetManyByUserIdAsync(Guid userId); Task<ICollection<Subvault>> GetManyByUserIdAsync(Guid userId);
} }

View File

@ -1,11 +1,12 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.Core.Models.Table; using Bit.Core.Models.Table;
using System.Collections.Generic;
namespace Bit.Core.Repositories namespace Bit.Core.Repositories
{ {
public interface ISubvaultUserRepository : IRepository<SubvaultUser, Guid> public interface ISubvaultUserRepository : IRepository<SubvaultUser, Guid>
{ {
Task<ICollection<SubvaultUser>> GetManyByOrganizationUserIdAsync(Guid orgUserId);
} }
} }

View File

@ -45,6 +45,19 @@ namespace Bit.Core.Repositories.SqlServer
} }
} }
public async Task<ICollection<Subvault>> GetManyByOrganizationIdAsync(Guid organizationId)
{
using(var connection = new SqlConnection(ConnectionString))
{
var results = await connection.QueryAsync<Subvault>(
$"[{Schema}].[{Table}_ReadByOrganizationId]",
new { OrganizationId = organizationId },
commandType: CommandType.StoredProcedure);
return results.ToList();
}
}
public async Task<ICollection<Subvault>> GetManyByUserIdAsync(Guid userId) public async Task<ICollection<Subvault>> GetManyByUserIdAsync(Guid userId)
{ {
using(var connection = new SqlConnection(ConnectionString)) using(var connection = new SqlConnection(ConnectionString))

View File

@ -1,5 +1,11 @@
using System; using System;
using Bit.Core.Models.Table; using Bit.Core.Models.Table;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Data;
using System.Data.SqlClient;
using Dapper;
using System.Linq;
namespace Bit.Core.Repositories.SqlServer namespace Bit.Core.Repositories.SqlServer
{ {
@ -12,5 +18,18 @@ namespace Bit.Core.Repositories.SqlServer
public SubvaultUserRepository(string connectionString) public SubvaultUserRepository(string connectionString)
: base(connectionString) : base(connectionString)
{ } { }
public async Task<ICollection<SubvaultUser>> GetManyByOrganizationUserIdAsync(Guid orgUserId)
{
using(var connection = new SqlConnection(ConnectionString))
{
var results = await connection.QueryAsync<SubvaultUser>(
$"[{Schema}].[{Table}_ReadByOrganizationUserId]",
new { OrganizationUserId = orgUserId },
commandType: CommandType.StoredProcedure);
return results.ToList();
}
}
} }
} }

View File

@ -2,6 +2,7 @@
using Bit.Core.Models.Business; using Bit.Core.Models.Business;
using Bit.Core.Models.Table; using Bit.Core.Models.Table;
using System; using System;
using System.Collections.Generic;
namespace Bit.Core.Services namespace Bit.Core.Services
{ {
@ -11,5 +12,6 @@ namespace Bit.Core.Services
Task<OrganizationUser> InviteUserAsync(Guid organizationId, string email); Task<OrganizationUser> InviteUserAsync(Guid organizationId, string email);
Task<OrganizationUser> AcceptUserAsync(Guid organizationUserId, User user, string token); Task<OrganizationUser> AcceptUserAsync(Guid organizationUserId, User user, string token);
Task<OrganizationUser> ConfirmUserAsync(Guid organizationUserId, string key); Task<OrganizationUser> ConfirmUserAsync(Guid organizationUserId, string key);
Task<OrganizationUser> SaveUserAsync(OrganizationUser user, IEnumerable<SubvaultUser> subvaults);
} }
} }

View File

@ -6,6 +6,7 @@ using Bit.Core.Models.Business;
using Bit.Core.Models.Table; using Bit.Core.Models.Table;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using System.Collections.Generic;
namespace Bit.Core.Services namespace Bit.Core.Services
{ {
@ -13,15 +14,21 @@ namespace Bit.Core.Services
{ {
private readonly IOrganizationRepository _organizationRepository; private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly ISubvaultRepository _subvaultRepository;
private readonly ISubvaultUserRepository _subvaultUserRepository;
private readonly IUserRepository _userRepository; private readonly IUserRepository _userRepository;
public OrganizationService( public OrganizationService(
IOrganizationRepository organizationRepository, IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository, IOrganizationUserRepository organizationUserRepository,
ISubvaultRepository subvaultRepository,
ISubvaultUserRepository subvaultUserRepository,
IUserRepository userRepository) IUserRepository userRepository)
{ {
_organizationRepository = organizationRepository; _organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository; _organizationUserRepository = organizationUserRepository;
_subvaultRepository = subvaultRepository;
_subvaultUserRepository = subvaultUserRepository;
_userRepository = userRepository; _userRepository = userRepository;
} }
@ -141,5 +148,37 @@ namespace Bit.Core.Services
return orgUser; return orgUser;
} }
public async Task<OrganizationUser> SaveUserAsync(OrganizationUser user, IEnumerable<SubvaultUser> subvaults)
{
if(user.Id.Equals(default(Guid)))
{
throw new BadRequestException("Invite the user first.");
}
await _organizationUserRepository.ReplaceAsync(user);
var orgSubvaults = await _subvaultRepository.GetManyByOrganizationIdAsync(user.OrganizationId);
var currentUserSubvaults = await _subvaultUserRepository.GetManyByOrganizationUserIdAsync(user.Id);
// Let's make sure all these belong to this user and organization.
var filteredSubvaults = subvaults.Where(s =>
orgSubvaults.Any(os => os.Id == s.SubvaultId) &&
(s.Id == default(Guid) || currentUserSubvaults.Any(cs => cs.Id == s.Id)));
var subvaultsToDelete = currentUserSubvaults.Where(cs => !subvaults.Any(s => s.Id == cs.Id));
foreach(var subvault in filteredSubvaults)
{
await _subvaultUserRepository.UpsertAsync(subvault);
}
foreach(var subvault in subvaultsToDelete)
{
await _subvaultUserRepository.DeleteAsync(subvault);
}
return user;
}
} }
} }

View File

@ -78,6 +78,10 @@
<Build Include="dbo\Tables\Favorite.sql" /> <Build Include="dbo\Tables\Favorite.sql" />
<Build Include="dbo\Tables\User.sql" /> <Build Include="dbo\Tables\User.sql" />
<Build Include="dbo\Tables\Folder.sql" /> <Build Include="dbo\Tables\Folder.sql" />
<Build Include="dbo\Tables\Group.sql" />
<Build Include="dbo\Tables\GroupUser.sql" />
<Build Include="dbo\Tables\SubvaultGroup.sql" />
<Build Include="dbo\Views\SubvaultUserView.sql" />
<Build Include="dbo\Views\DeviceView.sql" /> <Build Include="dbo\Views\DeviceView.sql" />
<Build Include="dbo\Views\HistoryView.sql" /> <Build Include="dbo\Views\HistoryView.sql" />
<Build Include="dbo\Views\CipherView.sql" /> <Build Include="dbo\Views\CipherView.sql" />
@ -85,6 +89,11 @@
<Build Include="dbo\Views\OrganizationView.sql" /> <Build Include="dbo\Views\OrganizationView.sql" />
<Build Include="dbo\Views\UserView.sql" /> <Build Include="dbo\Views\UserView.sql" />
<Build Include="dbo\Views\GrantView.sql" /> <Build Include="dbo\Views\GrantView.sql" />
<Build Include="dbo\Views\OrganizationUserOrganizationDetailsView.sql" />
<Build Include="dbo\Views\OrganizationUserUserDetailsView.sql" />
<Build Include="dbo\Views\SubvaultView.sql" />
<Build Include="dbo\Stored Procedures\Subvault_ReadByOrganizationId.sql" />
<Build Include="dbo\Stored Procedures\SubvaultUser_ReadByOrganizationUserId.sql" />
<Build Include="dbo\Stored Procedures\Cipher_Create.sql" /> <Build Include="dbo\Stored Procedures\Cipher_Create.sql" />
<Build Include="dbo\Stored Procedures\Cipher_DeleteById.sql" /> <Build Include="dbo\Stored Procedures\Cipher_DeleteById.sql" />
<Build Include="dbo\Stored Procedures\Favorite_Create.sql" /> <Build Include="dbo\Stored Procedures\Favorite_Create.sql" />
@ -142,14 +151,11 @@
<Build Include="dbo\Stored Procedures\Grant_ReadBySubjectId.sql" /> <Build Include="dbo\Stored Procedures\Grant_ReadBySubjectId.sql" />
<Build Include="dbo\Stored Procedures\Grant_Save.sql" /> <Build Include="dbo\Stored Procedures\Grant_Save.sql" />
<Build Include="dbo\Stored Procedures\User_ReadAccountRevisionDateById.sql" /> <Build Include="dbo\Stored Procedures\User_ReadAccountRevisionDateById.sql" />
<Build Include="dbo\Views\OrganizationUserUserDetailsView.sql" />
<Build Include="dbo\Stored Procedures\OrganizationUserUserDetails_ReadById.sql" />
<Build Include="dbo\Stored Procedures\OrganizationUserUserDetails_ReadByOrganizationId.sql" />
<Build Include="dbo\Stored Procedures\User_ReadPublicKeyById.sql" /> <Build Include="dbo\Stored Procedures\User_ReadPublicKeyById.sql" />
<Build Include="dbo\Stored Procedures\OrganizationUserOrganizationDetails_ReadByUserId.sql" /> <Build Include="dbo\Stored Procedures\OrganizationUserOrganizationDetails_ReadByUserId.sql" />
<Build Include="dbo\Views\OrganizationUserOrganizationDetailsView.sql" /> <Build Include="dbo\Stored Procedures\OrganizationUserUserDetails_ReadById.sql" />
<Build Include="dbo\Stored Procedures\OrganizationUserUserDetails_ReadByOrganizationId.sql" />
<Build Include="dbo\Stored Procedures\Subvault_ReadByIdAdminUserId.sql" /> <Build Include="dbo\Stored Procedures\Subvault_ReadByIdAdminUserId.sql" />
<Build Include="dbo\Views\SubvaultView.sql" />
<Build Include="dbo\Stored Procedures\Subvault_ReadByUserId.sql" /> <Build Include="dbo\Stored Procedures\Subvault_ReadByUserId.sql" />
<Build Include="dbo\Stored Procedures\Subvault_ReadByOrganizationIdAdminUserId.sql" /> <Build Include="dbo\Stored Procedures\Subvault_ReadByOrganizationIdAdminUserId.sql" />
</ItemGroup> </ItemGroup>

View File

@ -1,7 +1,7 @@
CREATE PROCEDURE [dbo].[SubvaultUser_Create] CREATE PROCEDURE [dbo].[SubvaultUser_Create]
@Id UNIQUEIDENTIFIER, @Id UNIQUEIDENTIFIER,
@SubvaultId UNIQUEIDENTIFIER, @SubvaultId UNIQUEIDENTIFIER,
@UserId UNIQUEIDENTIFIER, @OrganizationUserId UNIQUEIDENTIFIER,
@Admin BIT, @Admin BIT,
@ReadOnly BIT, @ReadOnly BIT,
@CreationDate DATETIME2(7), @CreationDate DATETIME2(7),
@ -14,7 +14,7 @@ BEGIN
( (
[Id], [Id],
[SubvaultId], [SubvaultId],
[UserId], [OrganizationUserId],
[Admin], [Admin],
[ReadOnly], [ReadOnly],
[CreationDate], [CreationDate],
@ -24,7 +24,7 @@ BEGIN
( (
@Id, @Id,
@SubvaultId, @SubvaultId,
@UserId, @OrganizationUserId,
@Admin, @Admin,
@ReadOnly, @ReadOnly,
@CreationDate, @CreationDate,

View File

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

View File

@ -1,7 +1,7 @@
CREATE PROCEDURE [dbo].[SubvaultUser_Update] CREATE PROCEDURE [dbo].[SubvaultUser_Update]
@Id UNIQUEIDENTIFIER, @Id UNIQUEIDENTIFIER,
@SubvaultId UNIQUEIDENTIFIER, @SubvaultId UNIQUEIDENTIFIER,
@UserId UNIQUEIDENTIFIER, @OrganizationUserId UNIQUEIDENTIFIER,
@Key VARCHAR(MAX), @Key VARCHAR(MAX),
@Admin BIT, @Admin BIT,
@ReadOnly BIT, @ReadOnly BIT,
@ -15,7 +15,7 @@ BEGIN
[dbo].[SubvaultUser] [dbo].[SubvaultUser]
SET SET
[SubvaultId] = @SubvaultId, [SubvaultId] = @SubvaultId,
[UserId] = @UserId, [OrganizationUserId] = @OrganizationUserId,
[Admin] = @Admin, [Admin] = @Admin,
[ReadOnly] = @ReadOnly, [ReadOnly] = @ReadOnly,
[CreationDate] = @CreationDate, [CreationDate] = @CreationDate,

View File

@ -0,0 +1,13 @@
CREATE PROCEDURE [dbo].[Subvault_ReadByOrganizationId]
@OrganizationId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
SELECT
S.*
FROM
[dbo].[SubvaultView] S
WHERE
S.[OrganizationId] = @OrganizationId
END

View File

@ -10,6 +10,8 @@ BEGIN
[dbo].[SubvaultView] S [dbo].[SubvaultView] S
INNER JOIN INNER JOIN
[SubvaultUser] SU ON SU.[SubvaultId] = S.[Id] [SubvaultUser] SU ON SU.[SubvaultId] = S.[Id]
INNER JOIN
[OrganizationUser] OU ON SU.[OrganizationUserId] = OU.[Id]
WHERE WHERE
SU.[UserId] = @UserId OU.[UserId] = @UserId
END END

View File

@ -0,0 +1,10 @@
CREATE TABLE [dbo].[Group] (
[Id] UNIQUEIDENTIFIER NOT NULL,
[OrganizationId] UNIQUEIDENTIFIER NOT NULL,
[Name] NVARCHAR (50) NOT NULL,
[CreationDate] DATETIME NOT NULL,
[RevisionDate] DATETIME NOT NULL,
CONSTRAINT [PK_Group] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_Group_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id])
);

View File

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

View File

@ -0,0 +1,8 @@
CREATE TABLE [dbo].[SubvaultGroup] (
[SubvaultId] UNIQUEIDENTIFIER NOT NULL,
[GroupId] UNIQUEIDENTIFIER NOT NULL,
CONSTRAINT [PK_SubvaultGroup] PRIMARY KEY CLUSTERED ([SubvaultId] ASC, [GroupId] ASC),
CONSTRAINT [FK_SubvaultGroup_Group] FOREIGN KEY ([GroupId]) REFERENCES [dbo].[Group] ([Id]),
CONSTRAINT [FK_SubvaultGroup_Subvault] FOREIGN KEY ([SubvaultId]) REFERENCES [dbo].[Subvault] ([Id])
);

View File

@ -1,13 +1,13 @@
CREATE TABLE [dbo].[SubvaultUser] ( CREATE TABLE [dbo].[SubvaultUser] (
[Id] UNIQUEIDENTIFIER NOT NULL, [Id] UNIQUEIDENTIFIER NOT NULL,
[SubvaultId] UNIQUEIDENTIFIER NOT NULL, [SubvaultId] UNIQUEIDENTIFIER NOT NULL,
[UserId] UNIQUEIDENTIFIER NOT NULL, [OrganizationUserId] UNIQUEIDENTIFIER NOT NULL,
[Admin] BIT NOT NULL, [Admin] BIT NOT NULL,
[ReadOnly] BIT NOT NULL, [ReadOnly] BIT NOT NULL,
[CreationDate] DATETIME2 (7) NOT NULL, [CreationDate] DATETIME2 (7) NOT NULL,
[RevisionDate] DATETIME2 (7) NOT NULL, [RevisionDate] DATETIME2 (7) NOT NULL,
CONSTRAINT [PK_SubvaultUser] PRIMARY KEY CLUSTERED ([Id] ASC), CONSTRAINT [PK_SubvaultUser] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_SubvaultUser_Subvault] FOREIGN KEY ([SubvaultId]) REFERENCES [dbo].[Subvault] ([Id]), CONSTRAINT [FK_SubvaultUser_OrganizationUser] FOREIGN KEY ([OrganizationUserId]) REFERENCES [dbo].[OrganizationUser] ([Id]),
CONSTRAINT [FK_SubvaultUser_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]) CONSTRAINT [FK_SubvaultUser_Subvault] FOREIGN KEY ([SubvaultId]) REFERENCES [dbo].[Subvault] ([Id])
); );

View File

@ -0,0 +1,6 @@
CREATE VIEW [dbo].[SubvaultUserView]
AS
SELECT
*
FROM
[dbo].[SubvaultUser]