1
0
mirror of https://github.com/bitwarden/server.git synced 2025-05-29 15:24:51 -05:00

user can edit responses and cipher partial updates

This commit is contained in:
Kyle Spearrin 2017-03-24 16:15:50 -04:00
parent 84c5873cfd
commit 5029af33c5
17 changed files with 177 additions and 38 deletions

View File

@ -104,6 +104,15 @@ namespace Bit.Api.Controllers
// await _cipherService.SaveAsync(cipher); // await _cipherService.SaveAsync(cipher);
//} //}
[HttpPut("{id}/partial")]
[HttpPost("{id}/partial")]
public async Task PutPartial(string id, [FromBody]CipherPartialRequestModel model)
{
var userId = _userService.GetProperUserId(User).Value;
var folderId = string.IsNullOrWhiteSpace(model.FolderId) ? null : (Guid?)new Guid(model.FolderId);
await _cipherService.UpdatePartialAsync(new Guid(id), userId, folderId, model.Favorite);
}
[HttpPut("{id}/move")] [HttpPut("{id}/move")]
[HttpPost("{id}/move")] [HttpPost("{id}/move")]
public async Task PostMove(string id, [FromBody]CipherMoveRequestModel model) public async Task PostMove(string id, [FromBody]CipherMoveRequestModel model)

View File

@ -31,16 +31,16 @@ namespace Bit.Api.Controllers
} }
[HttpGet("{id}")] [HttpGet("{id}")]
public async Task<LoginResponseModel> Get(string id) public async Task<LoginDetailsResponseModel> Get(string id)
{ {
var userId = _userService.GetProperUserId(User).Value; var userId = _userService.GetProperUserId(User).Value;
var login = await _cipherRepository.GetByIdAsync(new Guid(id), userId); var login = await _cipherRepository.GetFullDetailsByIdAsync(new Guid(id), userId);
if(login == null || login.Type != Core.Enums.CipherType.Login) if(login == null || login.Type != Core.Enums.CipherType.Login)
{ {
throw new NotFoundException(); throw new NotFoundException();
} }
var response = new LoginResponseModel(login); var response = new LoginDetailsResponseModel(login);
return response; return response;
} }
@ -70,7 +70,7 @@ namespace Bit.Api.Controllers
public async Task<LoginResponseModel> Put(string id, [FromBody]LoginRequestModel model) public async Task<LoginResponseModel> Put(string id, [FromBody]LoginRequestModel model)
{ {
var userId = _userService.GetProperUserId(User).Value; var userId = _userService.GetProperUserId(User).Value;
var login = await _cipherRepository.GetByIdAsync(new Guid(id), _userService.GetProperUserId(User).Value); var login = await _cipherRepository.GetByIdAsync(new Guid(id), userId);
if(login == null || login.Type != Core.Enums.CipherType.Login) if(login == null || login.Type != Core.Enums.CipherType.Login)
{ {
throw new NotFoundException(); throw new NotFoundException();

View File

@ -0,0 +1,11 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Core.Models.Api
{
public class CipherPartialRequestModel
{
[StringLength(36)]
public string FolderId { get; set; }
public bool Favorite { get; set; }
}
}

View File

@ -43,4 +43,15 @@ namespace Bit.Core.Models.Api
public string Notes { get; set; } public string Notes { get; set; }
public DateTime RevisionDate { get; set; } public DateTime RevisionDate { get; set; }
} }
public class LoginDetailsResponseModel : LoginResponseModel
{
public LoginDetailsResponseModel(CipherFullDetails cipher)
: base(cipher, "loginDetails")
{
Edit = cipher.Edit;
}
public bool Edit { get; set; }
}
} }

View File

@ -0,0 +1,7 @@
namespace Core.Models.Data
{
public class CipherFullDetails : CipherDetails
{
public bool Edit { get; set; }
}
}

View File

@ -9,6 +9,7 @@ namespace Bit.Core.Repositories
public interface ICipherRepository : IRepository<Cipher, Guid> public interface ICipherRepository : IRepository<Cipher, Guid>
{ {
Task<CipherDetails> GetByIdAsync(Guid id, Guid userId); Task<CipherDetails> GetByIdAsync(Guid id, Guid userId);
Task<CipherFullDetails> GetFullDetailsByIdAsync(Guid id, Guid userId);
Task<ICollection<CipherDetails>> GetManyByUserIdAsync(Guid userId); Task<ICollection<CipherDetails>> GetManyByUserIdAsync(Guid userId);
Task<ICollection<CipherDetails>> GetManyByUserIdHasSubvaultsAsync(Guid userId); Task<ICollection<CipherDetails>> GetManyByUserIdHasSubvaultsAsync(Guid userId);
Task<ICollection<CipherDetails>> GetManyByTypeAndUserIdAsync(Enums.CipherType type, Guid userId); Task<ICollection<CipherDetails>> GetManyByTypeAndUserIdAsync(Enums.CipherType type, Guid userId);
@ -18,6 +19,7 @@ namespace Bit.Core.Repositories
Task ReplaceAsync(CipherDetails cipher); Task ReplaceAsync(CipherDetails cipher);
Task UpsertAsync(CipherDetails cipher); Task UpsertAsync(CipherDetails cipher);
Task ReplaceAsync(Cipher obj, IEnumerable<Guid> subvaultIds); Task ReplaceAsync(Cipher obj, IEnumerable<Guid> subvaultIds);
Task UpdatePartialAsync(Guid id, Guid userId, Guid? folderId, bool favorite);
Task UpdateUserEmailPasswordAndCiphersAsync(User user, IEnumerable<Cipher> ciphers); Task UpdateUserEmailPasswordAndCiphersAsync(User user, IEnumerable<Cipher> ciphers);
Task CreateAsync(IEnumerable<Cipher> ciphers); Task CreateAsync(IEnumerable<Cipher> ciphers);
} }

View File

@ -12,6 +12,6 @@ namespace Bit.Core.Repositories
Task<ICollection<SubvaultUserDetails>> GetManyDetailsByUserIdAsync(Guid userId); Task<ICollection<SubvaultUserDetails>> GetManyDetailsByUserIdAsync(Guid userId);
Task<ICollection<SubvaultUserPermissions>> GetPermissionsByUserIdAsync(Guid userId, IEnumerable<Guid> subvaultIds, Task<ICollection<SubvaultUserPermissions>> GetPermissionsByUserIdAsync(Guid userId, IEnumerable<Guid> subvaultIds,
Guid organizationId); Guid organizationId);
Task<bool> GetIsAdminByUserIdCipherIdAsync(Guid userId, Guid cipherId); Task<bool> GetCanEditByUserIdCipherIdAsync(Guid userId, Guid cipherId);
} }
} }

View File

@ -36,6 +36,19 @@ namespace Bit.Core.Repositories.SqlServer
} }
} }
public async Task<CipherFullDetails> GetFullDetailsByIdAsync(Guid id, Guid userId)
{
using(var connection = new SqlConnection(ConnectionString))
{
var results = await connection.QueryAsync<CipherFullDetails>(
$"[{Schema}].[CipherFullDetails_ReadByIdUserId]",
new { Id = id, UserId = userId },
commandType: CommandType.StoredProcedure);
return results.FirstOrDefault();
}
}
public async Task<ICollection<CipherDetails>> GetManyByUserIdAsync(Guid userId) public async Task<ICollection<CipherDetails>> GetManyByUserIdAsync(Guid userId)
{ {
using(var connection = new SqlConnection(ConnectionString)) using(var connection = new SqlConnection(ConnectionString))
@ -149,6 +162,17 @@ namespace Bit.Core.Repositories.SqlServer
} }
} }
public async Task UpdatePartialAsync(Guid id, Guid userId, Guid? folderId, bool favorite)
{
using(var connection = new SqlConnection(ConnectionString))
{
var results = await connection.ExecuteAsync(
$"[{Schema}].[Cipher_UpdatePartial]",
new { Id = id, UserId = userId, FolderId = folderId, Favorite = favorite },
commandType: CommandType.StoredProcedure);
}
}
public Task UpdateUserEmailPasswordAndCiphersAsync(User user, IEnumerable<Cipher> ciphers) public Task UpdateUserEmailPasswordAndCiphersAsync(User user, IEnumerable<Cipher> ciphers)
{ {
if(ciphers.Count() == 0) if(ciphers.Count() == 0)

View File

@ -61,12 +61,12 @@ namespace Bit.Core.Repositories.SqlServer
} }
} }
public async Task<bool> GetIsAdminByUserIdCipherIdAsync(Guid userId, Guid cipherId) public async Task<bool> GetCanEditByUserIdCipherIdAsync(Guid userId, Guid cipherId)
{ {
using(var connection = new SqlConnection(ConnectionString)) using(var connection = new SqlConnection(ConnectionString))
{ {
var result = await connection.QueryFirstOrDefaultAsync<bool>( var result = await connection.QueryFirstOrDefaultAsync<bool>(
$"[{Schema}].[SubvaultUser_ReadIsAdminByCipherIdUserId]", $"[{Schema}].[SubvaultUser_ReadCanEditByCipherIdUserId]",
new { UserId = userId, CipherId = cipherId }, new { UserId = userId, CipherId = cipherId },
commandType: CommandType.StoredProcedure); commandType: CommandType.StoredProcedure);

View File

@ -9,6 +9,7 @@ namespace Bit.Core.Services
public interface ICipherService public interface ICipherService
{ {
Task SaveAsync(CipherDetails cipher, Guid savingUserId); Task SaveAsync(CipherDetails cipher, Guid savingUserId);
Task UpdatePartialAsync(Guid cipherId, Guid savingUserId, Guid? folderId, bool favorite);
Task DeleteAsync(CipherDetails cipher, Guid deletingUserId); Task DeleteAsync(CipherDetails cipher, Guid deletingUserId);
Task SaveFolderAsync(Folder folder); Task SaveFolderAsync(Folder folder);
Task DeleteFolderAsync(Folder folder); Task DeleteFolderAsync(Folder folder);

View File

@ -39,7 +39,7 @@ namespace Bit.Core.Services
public async Task SaveAsync(CipherDetails cipher, Guid savingUserId) public async Task SaveAsync(CipherDetails cipher, Guid savingUserId)
{ {
if(!(await UserHasAdminRights(cipher, savingUserId))) if(!(await UserCanEditAsync(cipher, savingUserId)))
{ {
throw new BadRequestException("Not an admin."); throw new BadRequestException("Not an admin.");
} }
@ -62,9 +62,19 @@ namespace Bit.Core.Services
} }
} }
public async Task UpdatePartialAsync(Guid cipherId, Guid savingUserId, Guid? folderId, bool favorite)
{
if(!(await UserCanPartialEditAsync(cipherId, savingUserId)))
{
throw new BadRequestException("Cannot edit.");
}
await _cipherRepository.UpdatePartialAsync(cipherId, savingUserId, folderId, favorite);
}
public async Task DeleteAsync(CipherDetails cipher, Guid deletingUserId) public async Task DeleteAsync(CipherDetails cipher, Guid deletingUserId)
{ {
if(!(await UserHasAdminRights(cipher, deletingUserId))) if(!(await UserCanEditAsync(cipher, deletingUserId)))
{ {
throw new BadRequestException("Not an admin."); throw new BadRequestException("Not an admin.");
} }
@ -163,14 +173,22 @@ namespace Bit.Core.Services
} }
} }
private async Task<bool> UserHasAdminRights(CipherDetails cipher, Guid userId) private async Task<bool> UserCanEditAsync(CipherDetails cipher, Guid userId)
{ {
if(!cipher.OrganizationId.HasValue && cipher.UserId.HasValue && cipher.UserId.Value == userId) if(!cipher.OrganizationId.HasValue && cipher.UserId.HasValue && cipher.UserId.Value == userId)
{ {
return true; return true;
} }
return await _subvaultUserRepository.GetIsAdminByUserIdCipherIdAsync(userId, cipher.Id); return await _subvaultUserRepository.GetCanEditByUserIdCipherIdAsync(userId, cipher.Id);
}
private Task<bool> UserCanPartialEditAsync(Guid cipherId, Guid userId)
{
// TODO: implement
return Task.FromResult(true);
//return await _subvaultUserRepository.GetCanEditByUserIdCipherIdAsync(userId, cipherId);
} }
} }
} }

View File

@ -173,6 +173,9 @@
<Build Include="dbo\Stored Procedures\OrganizationUserUserDetails_ReadByOrganizationId.sql" /> <Build Include="dbo\Stored Procedures\OrganizationUserUserDetails_ReadByOrganizationId.sql" />
<Build Include="dbo\Stored Procedures\Subvault_ReadByOrganizationIdAdminUserId.sql" /> <Build Include="dbo\Stored Procedures\Subvault_ReadByOrganizationIdAdminUserId.sql" />
<Build Include="dbo\User Defined Types\GuidIdArray.sql" /> <Build Include="dbo\User Defined Types\GuidIdArray.sql" />
<Build Include="dbo\Stored Procedures\SubvaultUser_ReadIsAdminByCipherIdUserId.sql" /> <Build Include="dbo\Stored Procedures\SubvaultUser_ReadCanEditByCipherIdUserId.sql" />
<Build Include="dbo\Stored Procedures\CipherFullDetails_ReadByIdUserId.sql" />
<Build Include="dbo\Functions\UserCanEditCipher.sql" />
<Build Include="dbo\Stored Procedures\Cipher_UpdatePartial.sql" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,13 +1,15 @@
CREATE PROCEDURE [dbo].[SubvaultUser_ReadIsAdminByCipherIdUserId] CREATE FUNCTION [dbo].[UserCanEditCipher](@UserId UNIQUEIDENTIFIER, @CipherId UNIQUEIDENTIFIER)
@UserId UNIQUEIDENTIFIER, RETURNS BIT AS
@CipherId AS UNIQUEIDENTIFIER
AS
BEGIN BEGIN
SET NOCOUNT ON DECLARE @CanEdit BIT
;WITH [CTE] AS( ;WITH [CTE] AS(
SELECT SELECT
CASE WHEN OU.[Type] = 2 THEN SU.[Admin] ELSE 1 END AS [Admin] -- 2 = Regular User CASE
WHEN OU.[Type] = 2 AND SU.[Admin] = 1 THEN 1 -- 2 = Regular User
WHEN SU.[ReadOnly] = 0 THEN 1
ELSE 0
END [CanEdit]
FROM FROM
[dbo].[SubvaultUser] SU [dbo].[SubvaultUser] SU
INNER JOIN INNER JOIN
@ -22,9 +24,11 @@ BEGIN
AND OU.[Status] = 2 -- 2 = Confirmed AND OU.[Status] = 2 -- 2 = Confirmed
) )
SELECT SELECT
CASE WHEN COUNT(1) > 0 THEN 1 ELSE 0 END @CanEdit = CASE WHEN COUNT(1) > 0 THEN 1 ELSE 0 END
FROM FROM
[CTE] [CTE]
WHERE WHERE
[Admin] = 1 [CanEdit] = 1
END
RETURN @CanEdit
END

View File

@ -24,21 +24,5 @@ BEGIN
WHERE WHERE
[Id] = @Id [Id] = @Id
IF @FolderId IS NULL EXEC [dbo].[Cipher_UpdatePartial] @Id, @UserId, @FolderId, @Favorite
BEGIN
EXEC [dbo].[FolderCipher_DeleteByUserId] @UserId, @Id
END
ELSE IF (SELECT COUNT(1) FROM [dbo].[FolderCipher] WHERE [FolderId] = @FolderId AND [CipherId] = @Id) = 0
BEGIN
EXEC [dbo].[FolderCipher_Create] @FolderId, @Id
END
IF @Favorite = 0
BEGIN
EXEC [dbo].[Favorite_Delete] @UserId, @Id
END
ELSE IF (SELECT COUNT(1) FROM [dbo].[Favorite] WHERE [UserId] = @UserId AND [CipherId] = @Id) = 0
BEGIN
EXEC [dbo].[Favorite_Create] @UserId, @Id
END
END END

View File

@ -0,0 +1,28 @@
CREATE PROCEDURE [dbo].[CipherFullDetails_ReadByIdUserId]
@Id UNIQUEIDENTIFIER,
@UserId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
SELECT DISTINCT
C.*,
CASE
WHEN C.[OrganizationId] IS NULL THEN 1
ELSE [dbo].[UserCanEditCipher](@UserId, @Id)
END [Edit]
FROM
[dbo].[CipherDetails](@UserId) C
LEFT JOIN
[dbo].[SubvaultCipher] SC ON SC.[CipherId] = C.[Id]
LEFT JOIN
[dbo].[SubvaultUser] SU ON SU.[SubvaultId] = SC.[SubvaultId]
LEFT JOIN
[dbo].[OrganizationUser] OU ON OU.[Id] = SU.[OrganizationUserId]
WHERE
C.Id = @Id
AND (
(C.[UserId] IS NOT NULL AND C.[UserId] = @UserId)
OR (OU.[UserId] = @UserId AND OU.[Status] = 2) -- 2 = Confirmed
)
END

View File

@ -0,0 +1,27 @@
CREATE PROCEDURE [dbo].[Cipher_UpdatePartial]
@Id UNIQUEIDENTIFIER,
@UserId UNIQUEIDENTIFIER,
@FolderId UNIQUEIDENTIFIER,
@Favorite TINYINT
AS
BEGIN
SET NOCOUNT ON
IF @FolderId IS NULL
BEGIN
EXEC [dbo].[FolderCipher_DeleteByUserId] @UserId, @Id
END
ELSE IF (SELECT COUNT(1) FROM [dbo].[FolderCipher] WHERE [FolderId] = @FolderId AND [CipherId] = @Id) = 0
BEGIN
EXEC [dbo].[FolderCipher_Create] @FolderId, @Id
END
IF @Favorite = 0
BEGIN
EXEC [dbo].[Favorite_Delete] @UserId, @Id
END
ELSE IF (SELECT COUNT(1) FROM [dbo].[Favorite] WHERE [UserId] = @UserId AND [CipherId] = @Id) = 0
BEGIN
EXEC [dbo].[Favorite_Create] @UserId, @Id
END
END

View File

@ -0,0 +1,10 @@
CREATE PROCEDURE [dbo].[SubvaultUser_ReadCanEditByCipherIdUserId]
@UserId UNIQUEIDENTIFIER,
@CipherId AS UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
SELECT
[dbo].[UserCanEditCipher](@UserId, @CipherId)
END