diff --git a/.gitignore b/.gitignore index d16b12208b..3631680ffa 100644 --- a/.gitignore +++ b/.gitignore @@ -200,3 +200,4 @@ FakesAssemblies/ project.lock.json *.jfm mail_dist/ +*.refactorlog \ No newline at end of file diff --git a/src/Api/Controllers/CiphersController.cs b/src/Api/Controllers/CiphersController.cs index f818abedfe..51e3a8445a 100644 --- a/src/Api/Controllers/CiphersController.cs +++ b/src/Api/Controllers/CiphersController.cs @@ -33,29 +33,32 @@ namespace Bit.Api.Controllers [HttpGet("{id}")] public async Task Get(string id) { - var cipher = await _cipherRepository.GetByIdAsync(new Guid(id), _userService.GetProperUserId(User).Value); + var userId = _userService.GetProperUserId(User).Value; + var cipher = await _cipherRepository.GetByIdAsync(new Guid(id), userId); if(cipher == null) { throw new NotFoundException(); } - return new CipherResponseModel(cipher); + return new CipherResponseModel(cipher, userId); } [HttpGet("")] public async Task> Get() { - var ciphers = await _cipherRepository.GetManyByUserIdAsync(_userService.GetProperUserId(User).Value); - var responses = ciphers.Select(c => new CipherResponseModel(c)); + var userId = _userService.GetProperUserId(User).Value; + var ciphers = await _cipherRepository.GetManyByUserIdAsync(userId); + var responses = ciphers.Select(c => new CipherResponseModel(c, userId)); return new ListResponseModel(responses); } [HttpGet("history")] public async Task Get(DateTime since) { + var userId = _userService.GetProperUserId(User).Value; var history = await _cipherRepository.GetManySinceRevisionDateAndUserIdWithDeleteHistoryAsync( - since, _userService.GetProperUserId(User).Value); - return new CipherHistoryResponseModel(history.Item1, history.Item2); + since, userId); + return new CipherHistoryResponseModel(history.Item1, history.Item2, userId); } [HttpPost("import")] diff --git a/src/Api/Controllers/FoldersController.cs b/src/Api/Controllers/FoldersController.cs index 02363c9846..7f39430c4e 100644 --- a/src/Api/Controllers/FoldersController.cs +++ b/src/Api/Controllers/FoldersController.cs @@ -34,44 +34,48 @@ namespace Bit.Api.Controllers [HttpGet("{id}")] public async Task Get(string id) { + var userId = _userService.GetProperUserId(User).Value; var folder = await _cipherRepository.GetByIdAsync(new Guid(id), _userService.GetProperUserId(User).Value); if(folder == null || folder.Type != Core.Enums.CipherType.Folder) { throw new NotFoundException(); } - return new FolderResponseModel(folder); + return new FolderResponseModel(folder, userId); } [HttpGet("")] public async Task> Get() { - ICollection folders = await _cipherRepository.GetManyByTypeAndUserIdAsync(Core.Enums.CipherType.Folder, - _userService.GetProperUserId(User).Value); - var responses = folders.Select(f => new FolderResponseModel(f)); + var userId = _userService.GetProperUserId(User).Value; + ICollection folders = await _cipherRepository.GetManyByTypeAndUserIdAsync(Core.Enums.CipherType.Folder, + userId); + var responses = folders.Select(f => new FolderResponseModel(f, userId)); return new ListResponseModel(responses); } [HttpPost("")] public async Task Post([FromBody]FolderRequestModel model) { + var userId = _userService.GetProperUserId(User).Value; var folder = model.ToCipher(_userService.GetProperUserId(User).Value); await _cipherService.SaveAsync(folder); - return new FolderResponseModel(folder); + return new FolderResponseModel(folder, userId); } [HttpPut("{id}")] [HttpPost("{id}")] public async Task Put(string id, [FromBody]FolderRequestModel model) { - var folder = await _cipherRepository.GetByIdAsync(new Guid(id), _userService.GetProperUserId(User).Value); + var userId = _userService.GetProperUserId(User).Value; + var folder = await _cipherRepository.GetByIdAsync(new Guid(id), userId); if(folder == null || folder.Type != Core.Enums.CipherType.Folder) { throw new NotFoundException(); } - + await _cipherService.SaveAsync(model.ToCipher(folder)); - return new FolderResponseModel(folder); + return new FolderResponseModel(folder, userId); } [HttpDelete("{id}")] diff --git a/src/Api/Controllers/LoginsController.cs b/src/Api/Controllers/LoginsController.cs index ef212dc52b..e702aed80b 100644 --- a/src/Api/Controllers/LoginsController.cs +++ b/src/Api/Controllers/LoginsController.cs @@ -36,35 +36,38 @@ namespace Bit.Api.Controllers [HttpGet("{id}")] public async Task Get(string id, string[] expand = null) { - var login = await _cipherRepository.GetByIdAsync(new Guid(id), _userService.GetProperUserId(User).Value); + var userId = _userService.GetProperUserId(User).Value; + var login = await _cipherRepository.GetByIdAsync(new Guid(id), userId); if(login == null || login.Type != Core.Enums.CipherType.Login) { throw new NotFoundException(); } - var response = new LoginResponseModel(login); - await ExpandAsync(login, response, expand, null); + var response = new LoginResponseModel(login, userId); + await ExpandAsync(login, response, expand, null, userId); return response; } [HttpGet("")] public async Task> Get(string[] expand = null) { + var userId = _userService.GetProperUserId(User).Value; ICollection logins = await _cipherRepository.GetManyByTypeAndUserIdAsync(Core.Enums.CipherType.Login, - _userService.GetProperUserId(User).Value); - var responses = logins.Select(s => new LoginResponseModel(s)).ToList(); - await ExpandManyAsync(logins, responses, expand, null); + userId); + var responses = logins.Select(s => new LoginResponseModel(s, userId)).ToList(); + await ExpandManyAsync(logins, responses, expand, null, userId); return new ListResponseModel(responses); } [HttpPost("")] public async Task Post([FromBody]LoginRequestModel model, string[] expand = null) { - var login = model.ToCipher(_userService.GetProperUserId(User).Value); + var userId = _userService.GetProperUserId(User).Value; + var login = model.ToCipher(userId); await _cipherService.SaveAsync(login); - var response = new LoginResponseModel(login); - await ExpandAsync(login, response, expand, null); + var response = new LoginResponseModel(login, userId); + await ExpandAsync(login, response, expand, null, userId); return response; } @@ -72,6 +75,7 @@ namespace Bit.Api.Controllers [HttpPost("{id}")] public async Task Put(string id, [FromBody]LoginRequestModel model, string[] expand = null) { + var userId = _userService.GetProperUserId(User).Value; var login = await _cipherRepository.GetByIdAsync(new Guid(id), _userService.GetProperUserId(User).Value); if(login == null || login.Type != Core.Enums.CipherType.Login) { @@ -80,8 +84,8 @@ namespace Bit.Api.Controllers await _cipherService.SaveAsync(model.ToCipher(login)); - var response = new LoginResponseModel(login); - await ExpandAsync(login, response, expand, null); + var response = new LoginResponseModel(login, userId); + await ExpandAsync(login, response, expand, null, userId); return response; } @@ -98,7 +102,7 @@ namespace Bit.Api.Controllers await _cipherService.DeleteAsync(login); } - private async Task ExpandAsync(Cipher login, LoginResponseModel response, string[] expand, Cipher folder) + private async Task ExpandAsync(Cipher login, LoginResponseModel response, string[] expand, Cipher folder, Guid userId) { if(expand == null || expand.Count() == 0) { @@ -112,12 +116,12 @@ namespace Bit.Api.Controllers folder = await _cipherRepository.GetByIdAsync(login.FolderId.Value); } - response.Folder = new FolderResponseModel(folder); + response.Folder = new FolderResponseModel(folder, userId); } } private async Task ExpandManyAsync(IEnumerable logins, ICollection responses, - string[] expand, IEnumerable folders) + string[] expand, IEnumerable folders, Guid userId) { if(expand == null || expand.Count() == 0) { @@ -148,7 +152,7 @@ namespace Bit.Api.Controllers continue; } - response.Folder = new FolderResponseModel(folder); + response.Folder = new FolderResponseModel(folder, userId); } } } diff --git a/src/Api/Models/Response/CipherHistoryResponseModel.cs b/src/Api/Models/Response/CipherHistoryResponseModel.cs index d2fab805b1..2b4bb3a00b 100644 --- a/src/Api/Models/Response/CipherHistoryResponseModel.cs +++ b/src/Api/Models/Response/CipherHistoryResponseModel.cs @@ -7,7 +7,7 @@ namespace Bit.Api.Models { public class CipherHistoryResponseModel : ResponseModel { - public CipherHistoryResponseModel(IEnumerable revisedCiphers, IEnumerable deletedIds) + public CipherHistoryResponseModel(IEnumerable revisedCiphers, IEnumerable deletedIds, Guid userId) : base("cipherHistory") { if(revisedCiphers == null) @@ -20,7 +20,7 @@ namespace Bit.Api.Models throw new ArgumentNullException(nameof(deletedIds)); } - Revised = revisedCiphers.Select(c => new CipherResponseModel(c)); + Revised = revisedCiphers.Select(c => new CipherResponseModel(c, userId)); Deleted = deletedIds.Select(id => id.ToString()); } diff --git a/src/Api/Models/Response/CipherResponseModel.cs b/src/Api/Models/Response/CipherResponseModel.cs index 300b921e9d..05208686cd 100644 --- a/src/Api/Models/Response/CipherResponseModel.cs +++ b/src/Api/Models/Response/CipherResponseModel.cs @@ -1,11 +1,14 @@ using System; using Bit.Core.Domains; +using System.Collections.Generic; +using Newtonsoft.Json; +using System.Linq; namespace Bit.Api.Models { public class CipherResponseModel : ResponseModel { - public CipherResponseModel(Cipher cipher) + public CipherResponseModel(Cipher cipher, Guid userId) : base("cipher") { if(cipher == null) @@ -30,6 +33,16 @@ namespace Bit.Api.Models default: throw new ArgumentException("Unsupported " + nameof(Type) + "."); } + + if(!string.IsNullOrWhiteSpace(cipher.Shares)) + { + var shares = JsonConvert.DeserializeObject>(cipher.Shares); + var userShare = shares.FirstOrDefault(s => s.UserId == userId); + if(userShare != null) + { + Key = userShare.Key; + } + } } public string Id { get; set; } @@ -37,6 +50,7 @@ namespace Bit.Api.Models public Core.Enums.CipherType Type { get; set; } public bool Favorite { get; set; } public dynamic Data { get; set; } + public string Key { get; set; } public DateTime RevisionDate { get; set; } } } diff --git a/src/Api/Models/Response/FolderResponseModel.cs b/src/Api/Models/Response/FolderResponseModel.cs index dbcc9ade9f..d6a46bba6f 100644 --- a/src/Api/Models/Response/FolderResponseModel.cs +++ b/src/Api/Models/Response/FolderResponseModel.cs @@ -1,11 +1,14 @@ using System; using Bit.Core.Domains; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Linq; namespace Bit.Api.Models { public class FolderResponseModel : ResponseModel { - public FolderResponseModel(Cipher cipher) + public FolderResponseModel(Cipher cipher, Guid userId) : base("folder") { if(cipher == null) @@ -23,10 +26,21 @@ namespace Bit.Api.Models Id = cipher.Id.ToString(); Name = data.Name; RevisionDate = cipher.RevisionDate; + + if(!string.IsNullOrWhiteSpace(cipher.Shares)) + { + var shares = JsonConvert.DeserializeObject>(cipher.Shares); + var userShare = shares.FirstOrDefault(s => s.UserId == userId); + if(userShare != null) + { + Key = userShare.Key; + } + } } public string Id { get; set; } public string Name { get; set; } + public string Key { get; set; } public DateTime RevisionDate { get; set; } } } diff --git a/src/Api/Models/Response/LoginResponseModel.cs b/src/Api/Models/Response/LoginResponseModel.cs index 60c8f0d1e3..97f25c0c41 100644 --- a/src/Api/Models/Response/LoginResponseModel.cs +++ b/src/Api/Models/Response/LoginResponseModel.cs @@ -1,11 +1,14 @@ using System; using Bit.Core.Domains; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Linq; namespace Bit.Api.Models { public class LoginResponseModel : ResponseModel { - public LoginResponseModel(Cipher cipher) + public LoginResponseModel(Cipher cipher, Guid userId) : base("login") { if(cipher == null) @@ -29,6 +32,16 @@ namespace Bit.Api.Models Password = data.Password; Notes = data.Notes; RevisionDate = cipher.RevisionDate; + + if(!string.IsNullOrWhiteSpace(cipher.Shares)) + { + var shares = JsonConvert.DeserializeObject>(cipher.Shares); + var userShare = shares.FirstOrDefault(s => s.UserId == userId); + if(userShare != null) + { + Key = userShare.Key; + } + } } public string Id { get; set; } @@ -39,6 +52,7 @@ namespace Bit.Api.Models public string Username { get; set; } public string Password { get; set; } public string Notes { get; set; } + public string Key { get; set; } public DateTime RevisionDate { get; set; } // Expandables diff --git a/src/Core/Domains/Cipher.cs b/src/Core/Domains/Cipher.cs index 46accfb29d..03130c2850 100644 --- a/src/Core/Domains/Cipher.cs +++ b/src/Core/Domains/Cipher.cs @@ -11,6 +11,7 @@ namespace Bit.Core.Domains public Enums.CipherType Type { get; set; } public bool Favorite { get; set; } public string Data { get; set; } + public string Shares { get; set; } public DateTime CreationDate { get; internal set; } = DateTime.UtcNow; public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow; @@ -18,5 +19,12 @@ namespace Bit.Core.Domains { Id = CoreHelpers.GenerateComb(); } + + public class Share + { + public Guid UserId { get; set; } + public string Key { get; set; } + // TODO: permission flags? + } } } diff --git a/src/Sql/Sql.sqlproj b/src/Sql/Sql.sqlproj index 4c4e23afb6..1c9f6845d8 100644 --- a/src/Sql/Sql.sqlproj +++ b/src/Sql/Sql.sqlproj @@ -104,4 +104,7 @@ + + + \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/Cipher_Create.sql b/src/Sql/dbo/Stored Procedures/Cipher_Create.sql index 4c7894909a..b51f336590 100644 --- a/src/Sql/dbo/Stored Procedures/Cipher_Create.sql +++ b/src/Sql/dbo/Stored Procedures/Cipher_Create.sql @@ -5,6 +5,7 @@ @Type TINYINT, @Favorite BIT, @Data NVARCHAR(MAX), + @Shares NVARCHAR(MAX), @CreationDate DATETIME2(7), @RevisionDate DATETIME2(7) AS @@ -19,6 +20,7 @@ BEGIN [Type], [Favorite], [Data], + [Shares], [CreationDate], [RevisionDate] ) @@ -30,6 +32,7 @@ BEGIN @Type, @Favorite, @Data, + @Shares, @CreationDate, @RevisionDate ) diff --git a/src/Sql/dbo/Stored Procedures/Cipher_Update.sql b/src/Sql/dbo/Stored Procedures/Cipher_Update.sql index 19190a73c6..8421ca20b6 100644 --- a/src/Sql/dbo/Stored Procedures/Cipher_Update.sql +++ b/src/Sql/dbo/Stored Procedures/Cipher_Update.sql @@ -5,6 +5,7 @@ @Type TINYINT, @Favorite BIT, @Data NVARCHAR(MAX), + @Shares NVARCHAR(MAX), @CreationDate DATETIME2(7), @RevisionDate DATETIME2(7) AS @@ -19,6 +20,7 @@ BEGIN [Type] = @Type, [Favorite] = @Favorite, [Data] = @Data, + [Shares] = @Shares, [CreationDate] = @CreationDate, [RevisionDate] = @RevisionDate WHERE diff --git a/src/Sql/dbo/Tables/Cipher.sql b/src/Sql/dbo/Tables/Cipher.sql index 719e147b09..0f8b2f2042 100644 --- a/src/Sql/dbo/Tables/Cipher.sql +++ b/src/Sql/dbo/Tables/Cipher.sql @@ -5,6 +5,7 @@ [Type] TINYINT NOT NULL, [Favorite] BIT NOT NULL, [Data] NVARCHAR (MAX) NOT NULL, + [Shares] NVARCHAR (MAX) NULL, [CreationDate] DATETIME2 (7) NOT NULL, [RevisionDate] DATETIME2 (7) NOT NULL, CONSTRAINT [PK_Cipher] PRIMARY KEY CLUSTERED ([Id] ASC),