diff --git a/src/Api/Controllers/CiphersController.cs b/src/Api/Controllers/CiphersController.cs index 579a3166d1..ad71540e94 100644 --- a/src/Api/Controllers/CiphersController.cs +++ b/src/Api/Controllers/CiphersController.cs @@ -10,6 +10,8 @@ using Bit.Core.Services; using Bit.Core; using Bit.Api.Utilities; using Bit.Core.Utilities; +using Core.Models.Data; +using System.Collections.Generic; namespace Bit.Api.Controllers { @@ -53,6 +55,19 @@ namespace Bit.Api.Controllers return new CipherResponseModel(cipher, _globalSettings); } + [HttpGet("{id}/admin")] + public async Task GetAdmin(string id) + { + var cipher = await _cipherRepository.GetDetailsByIdAsync(new Guid(id)); + if(cipher == null || !cipher.OrganizationId.HasValue || + !_currentContext.OrganizationAdmin(cipher.OrganizationId.Value)) + { + throw new NotFoundException(); + } + + return new CipherResponseModel(cipher, _globalSettings); + } + [HttpGet("{id}/full-details")] [HttpGet("{id}/details")] public async Task GetDetails(string id) @@ -70,14 +85,95 @@ namespace Bit.Api.Controllers } [HttpGet("")] - public async Task> Get() + public async Task> Get([FromQuery]Core.Enums.CipherType? type = null) { var userId = _userService.GetProperUserId(User).Value; - var ciphers = await _cipherRepository.GetManyByUserIdAsync(userId); + + IEnumerable ciphers; + if(type.HasValue) + { + ciphers = await _cipherRepository.GetManyByTypeAndUserIdAsync(Core.Enums.CipherType.Login, userId); + } + else + { + ciphers = await _cipherRepository.GetManyByUserIdAsync(userId); + } + var responses = ciphers.Select(c => new CipherResponseModel(c, _globalSettings)).ToList(); return new ListResponseModel(responses); } + [HttpPost("")] + public async Task Post([FromBody]CipherRequestModel model) + { + var userId = _userService.GetProperUserId(User).Value; + var cipher = model.ToCipherDetails(userId); + await _cipherService.SaveDetailsAsync(cipher, userId); + + var response = new CipherResponseModel(cipher, _globalSettings); + return response; + } + + [HttpPost("admin")] + public async Task PostAdmin([FromBody]CipherRequestModel model) + { + var cipher = model.ToOrganizationCipher(); + if(!_currentContext.OrganizationAdmin(cipher.OrganizationId.Value)) + { + throw new NotFoundException(); + } + + var userId = _userService.GetProperUserId(User).Value; + await _cipherService.SaveAsync(cipher, userId, true); + + var response = new CipherMiniResponseModel(cipher, _globalSettings, false); + return response; + } + + [HttpPut("{id}")] + [HttpPost("{id}")] + public async Task Put(string id, [FromBody]CipherRequestModel model) + { + var userId = _userService.GetProperUserId(User).Value; + var cipher = await _cipherRepository.GetByIdAsync(new Guid(id), userId); + if(cipher == null) + { + throw new NotFoundException(); + } + + var modelOrgId = string.IsNullOrWhiteSpace(model.OrganizationId) ? (Guid?)null : new Guid(model.OrganizationId); + if(cipher.OrganizationId != modelOrgId) + { + throw new BadRequestException("Organization mismatch. Re-sync if you recently shared this login, " + + "then try again."); + } + + await _cipherService.SaveDetailsAsync(model.ToCipherDetails(cipher), userId); + + var response = new CipherResponseModel(cipher, _globalSettings); + return response; + } + + [HttpPut("{id}/admin")] + [HttpPost("{id}/admin")] + public async Task PutAdmin(string id, [FromBody]CipherRequestModel model) + { + var userId = _userService.GetProperUserId(User).Value; + var cipher = await _cipherRepository.GetDetailsByIdAsync(new Guid(id)); + if(cipher == null || !cipher.OrganizationId.HasValue || + !_currentContext.OrganizationAdmin(cipher.OrganizationId.Value)) + { + throw new NotFoundException(); + } + + // object cannot be a descendant of CipherDetails, so let's clone it. + var cipherClone = CoreHelpers.CloneObject(model.ToCipher(cipher)); + await _cipherService.SaveAsync(cipherClone, userId, true); + + var response = new CipherMiniResponseModel(cipherClone, _globalSettings, cipher.OrganizationUseTotp); + return response; + } + [HttpGet("details")] public async Task> GetCollections() { diff --git a/src/Api/Controllers/LoginsController.cs b/src/Api/Controllers/LoginsController.cs index 7cc09af03f..fcd78a7e29 100644 --- a/src/Api/Controllers/LoginsController.cs +++ b/src/Api/Controllers/LoginsController.cs @@ -11,6 +11,7 @@ using Bit.Core; namespace Bit.Api.Controllers { + [Obsolete("Use ciphers API instead.")] [Route("logins")] // "sites" route is deprecated [Route("sites")] @@ -51,20 +52,6 @@ namespace Bit.Api.Controllers return response; } - [HttpGet("{id}/admin")] - public async Task GetAdmin(string id) - { - var login = await _cipherRepository.GetDetailsByIdAsync(new Guid(id)); - if(login == null || !login.OrganizationId.HasValue || - !_currentContext.OrganizationAdmin(login.OrganizationId.Value)) - { - throw new NotFoundException(); - } - - var response = new LoginResponseModel(login, _globalSettings, login.OrganizationUseTotp); - return response; - } - [HttpGet("")] public async Task> Get(string[] expand = null) { @@ -84,23 +71,7 @@ namespace Bit.Api.Controllers var response = new LoginResponseModel(login, _globalSettings); return response; } - - [HttpPost("admin")] - public async Task PostAdmin([FromBody]LoginRequestModel model) - { - var login = model.ToOrganizationCipher(); - if(!_currentContext.OrganizationAdmin(login.OrganizationId.Value)) - { - throw new NotFoundException(); - } - - var userId = _userService.GetProperUserId(User).Value; - await _cipherService.SaveAsync(login, userId, true); - - var response = new LoginResponseModel(login, _globalSettings, false); - return response; - } - + [HttpPut("{id}")] [HttpPost("{id}")] public async Task Put(string id, [FromBody]LoginRequestModel model) @@ -125,26 +96,6 @@ namespace Bit.Api.Controllers return response; } - [HttpPut("{id}/admin")] - [HttpPost("{id}/admin")] - public async Task PutAdmin(string id, [FromBody]LoginRequestModel model) - { - var userId = _userService.GetProperUserId(User).Value; - var login = await _cipherRepository.GetDetailsByIdAsync(new Guid(id)); - if(login == null || !login.OrganizationId.HasValue || - !_currentContext.OrganizationAdmin(login.OrganizationId.Value)) - { - throw new NotFoundException(); - } - - // object cannot be a descendant of CipherDetails, so let's clone it. - var cipher = Core.Utilities.CoreHelpers.CloneObject(model.ToCipher(login)); - await _cipherService.SaveAsync(cipher, userId, true); - - var response = new LoginResponseModel(cipher, _globalSettings, login.OrganizationUseTotp); - return response; - } - [HttpDelete("{id}")] [HttpPost("{id}/delete")] public async Task Delete(string id) diff --git a/src/Core/Models/Api/Request/Accounts/UpdateKeyRequestModel.cs b/src/Core/Models/Api/Request/Accounts/UpdateKeyRequestModel.cs index 21787c7a33..d895498a16 100644 --- a/src/Core/Models/Api/Request/Accounts/UpdateKeyRequestModel.cs +++ b/src/Core/Models/Api/Request/Accounts/UpdateKeyRequestModel.cs @@ -9,7 +9,7 @@ namespace Bit.Core.Models.Api [StringLength(300)] public string MasterPasswordHash { get; set; } [Required] - public IEnumerable Ciphers { get; set; } + public IEnumerable Ciphers { get; set; } [Required] public IEnumerable Folders { get; set; } [Required] diff --git a/src/Core/Models/Api/Request/CipherRequestModel.cs b/src/Core/Models/Api/Request/CipherRequestModel.cs index faa2c3ad8c..c9c8ff8b28 100644 --- a/src/Core/Models/Api/Request/CipherRequestModel.cs +++ b/src/Core/Models/Api/Request/CipherRequestModel.cs @@ -6,6 +6,7 @@ using Bit.Core.Enums; using Newtonsoft.Json; using System.Collections.Generic; using System.Linq; +using Core.Models.Data; namespace Bit.Core.Models.Api { @@ -13,11 +14,10 @@ namespace Bit.Core.Models.Api { public CipherType Type { get; set; } - [Required] - [StringLength(36)] - public string Id { get; set; } [StringLength(36)] public string OrganizationId { get; set; } + public string FolderId { get; set; } + public bool Favorite { get; set; } [Required] [EncryptedString] [StringLength(1000)] @@ -40,7 +40,28 @@ namespace Bit.Core.Models.Api public IEnumerable Fields { get; set; } public Dictionary Attachments { get; set; } - public virtual Cipher ToCipher(Cipher existingCipher) + public CipherDetails ToCipherDetails(Guid userId) + { + var cipher = new CipherDetails + { + Type = Type, + UserId = string.IsNullOrWhiteSpace(OrganizationId) ? (Guid?)userId : null, + OrganizationId = string.IsNullOrWhiteSpace(OrganizationId) ? (Guid?)null : new Guid(OrganizationId), + Edit = true + }; + ToCipherDetails(cipher); + return cipher; + } + + public CipherDetails ToCipherDetails(CipherDetails existingCipher) + { + existingCipher.FolderId = string.IsNullOrWhiteSpace(FolderId) ? null : (Guid?)new Guid(FolderId); + existingCipher.Favorite = Favorite; + ToCipher(existingCipher); + return existingCipher; + } + + public Cipher ToCipher(Cipher existingCipher) { switch(existingCipher.Type) { @@ -71,6 +92,46 @@ namespace Bit.Core.Models.Api existingCipher.SetAttachments(attachments); return existingCipher; } + + public CipherDetails ToOrganizationCipherDetails(Guid orgId) + { + return ToCipherDetails(new CipherDetails + { + Type = Type, + OrganizationId = orgId, + Edit = true + }); + } + + public Cipher ToOrganizationCipher() + { + if(string.IsNullOrWhiteSpace(OrganizationId)) + { + throw new ArgumentNullException(nameof(OrganizationId)); + } + + return ToCipher(new Cipher + { + Type = Type, + OrganizationId = new Guid(OrganizationId) + }); + } + } + + public class CipherWithIdRequestModel : CipherRequestModel + { + [Required] + [StringLength(36)] + public string Id { get; set; } + + public Cipher ToCipher(Guid userId) + { + return ToCipherDetails(new CipherDetails + { + UserId = userId, + Id = new Guid(Id) + }); + } } public class CipherShareRequestModel : IValidatableObject diff --git a/src/Core/Models/Api/Request/LoginRequestModel.cs b/src/Core/Models/Api/Request/LoginRequestModel.cs index aa2075023c..b5b87c0293 100644 --- a/src/Core/Models/Api/Request/LoginRequestModel.cs +++ b/src/Core/Models/Api/Request/LoginRequestModel.cs @@ -89,18 +89,4 @@ namespace Bit.Core.Models.Api return existingLogin; } } - - public class LoginWithIdRequestModel : LoginRequestModel - { - public Guid Id { get; set; } - - public Cipher ToCipher(Guid userId) - { - return ToCipherDetails(new CipherDetails - { - UserId = userId, - Id = Id - }); - } - } } diff --git a/src/Core/Models/Api/Response/CipherResponseModel.cs b/src/Core/Models/Api/Response/CipherResponseModel.cs index ddd2868883..96452d9edf 100644 --- a/src/Core/Models/Api/Response/CipherResponseModel.cs +++ b/src/Core/Models/Api/Response/CipherResponseModel.cs @@ -8,7 +8,7 @@ namespace Bit.Core.Models.Api { public class CipherMiniResponseModel : ResponseModel { - public CipherMiniResponseModel(Cipher cipher, GlobalSettings globalSettings, string obj = "cipherMini") + public CipherMiniResponseModel(Cipher cipher, GlobalSettings globalSettings, bool orgUseTotp, string obj = "cipherMini") : base(obj) { if(cipher == null) @@ -21,6 +21,7 @@ namespace Bit.Core.Models.Api RevisionDate = cipher.RevisionDate; OrganizationId = cipher.OrganizationId?.ToString(); Attachments = AttachmentResponseModel.FromCipher(cipher, globalSettings); + OrganizationUseTotp = orgUseTotp; switch(cipher.Type) { @@ -37,24 +38,23 @@ namespace Bit.Core.Models.Api public Enums.CipherType Type { get; set; } public dynamic Data { get; set; } public IEnumerable Attachments { get; set; } + public bool OrganizationUseTotp { get; set; } public DateTime RevisionDate { get; set; } } public class CipherResponseModel : CipherMiniResponseModel { public CipherResponseModel(CipherDetails cipher, GlobalSettings globalSettings, string obj = "cipher") - : base(cipher, globalSettings, obj) + : base(cipher, globalSettings, cipher.OrganizationUseTotp, obj) { FolderId = cipher.FolderId?.ToString(); Favorite = cipher.Favorite; Edit = cipher.Edit; - OrganizationUseTotp = cipher.OrganizationUseTotp; } public string FolderId { get; set; } public bool Favorite { get; set; } public bool Edit { get; set; } - public bool OrganizationUseTotp { get; set; } } public class CipherDetailsResponseModel : CipherResponseModel @@ -87,7 +87,7 @@ namespace Bit.Core.Models.Api { public CipherMiniDetailsResponseModel(Cipher cipher, GlobalSettings globalSettings, IDictionary> collectionCiphers, string obj = "cipherMiniDetails") - : base(cipher, globalSettings, obj) + : base(cipher, globalSettings, false, obj) { if(collectionCiphers.ContainsKey(cipher.Id)) {