diff --git a/src/Api/Controllers/LoginsController.cs b/src/Api/Controllers/LoginsController.cs index 72ddc9a733..d6cd6d6e35 100644 --- a/src/Api/Controllers/LoginsController.cs +++ b/src/Api/Controllers/LoginsController.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Authorization; using Bit.Core.Models.Api; using Bit.Core.Exceptions; using Bit.Core.Services; +using Bit.Core; namespace Bit.Api.Controllers { @@ -19,15 +20,18 @@ namespace Bit.Api.Controllers private readonly ICipherRepository _cipherRepository; private readonly ICipherService _cipherService; private readonly IUserService _userService; + private readonly CurrentContext _currentContext; public LoginsController( ICipherRepository cipherRepository, ICipherService cipherService, - IUserService userService) + IUserService userService, + CurrentContext currentContext) { _cipherRepository = cipherRepository; _cipherService = cipherService; _userService = userService; + _currentContext = currentContext; } [HttpGet("{id}")] @@ -44,6 +48,20 @@ namespace Bit.Api.Controllers return response; } + [HttpGet("{id}/admin")] + public async Task GetAdmin(string id) + { + var login = await _cipherRepository.GetByIdAsync(new Guid(id)); + if(login == null || !login.OrganizationId.HasValue || + !_currentContext.OrganizationAdmin(login.OrganizationId.Value)) + { + throw new NotFoundException(); + } + + var response = new LoginResponseModel(login); + return response; + } + [HttpGet("")] public async Task> Get() { @@ -59,7 +77,23 @@ namespace Bit.Api.Controllers { var userId = _userService.GetProperUserId(User).Value; var login = model.ToCipherDetails(userId); - await _cipherService.SaveAsync(login, userId); + await _cipherService.SaveDetailsAsync(login, userId); + + var response = new LoginResponseModel(login); + 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); return response; @@ -76,7 +110,25 @@ namespace Bit.Api.Controllers throw new NotFoundException(); } - await _cipherService.SaveAsync(model.ToCipherDetails(login), userId); + await _cipherService.SaveDetailsAsync(model.ToCipherDetails(login), userId); + + var response = new LoginResponseModel(login); + return response; + } + + [HttpPut("{id}/admin")] + [HttpPost("{id}/admin")] + public async Task PutAdmin(string id, [FromBody]LoginRequestModel model) + { + var login = await _cipherRepository.GetByIdAsync(new Guid(id)); + if(login == null || !login.OrganizationId.HasValue || + !_currentContext.OrganizationAdmin(login.OrganizationId.Value)) + { + throw new NotFoundException(); + } + + var userId = _userService.GetProperUserId(User).Value; + await _cipherService.SaveAsync(model.ToCipher(login), userId, true); var response = new LoginResponseModel(login); return response; diff --git a/src/Core/Models/Api/Request/LoginRequestModel.cs b/src/Core/Models/Api/Request/LoginRequestModel.cs index f511805c8e..e6520e7402 100644 --- a/src/Core/Models/Api/Request/LoginRequestModel.cs +++ b/src/Core/Models/Api/Request/LoginRequestModel.cs @@ -9,6 +9,8 @@ namespace Bit.Core.Models.Api { public class LoginRequestModel { + [StringLength(36)] + public string OrganizationId { get; set; } [StringLength(36)] public string FolderId { get; set; } public bool Favorite { get; set; } @@ -33,7 +35,21 @@ namespace Bit.Core.Models.Api { return ToCipherDetails(new CipherDetails { - UserId = userId + UserId = string.IsNullOrWhiteSpace(OrganizationId) ? (Guid?)userId : null, + OrganizationId = string.IsNullOrWhiteSpace(OrganizationId) ? (Guid?)null : new Guid(OrganizationId) + }); + } + + public Cipher ToOrganizationCipher() + { + if(string.IsNullOrWhiteSpace(OrganizationId)) + { + throw new ArgumentNullException(nameof(OrganizationId)); + } + + return ToCipher(new Cipher + { + OrganizationId = new Guid(OrganizationId) }); } @@ -48,6 +64,15 @@ namespace Bit.Core.Models.Api return existingLogin; } + + public Cipher ToCipher(Cipher existingLogin) + { + existingLogin.Data = JsonConvert.SerializeObject(new LoginDataModel(this), + new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + existingLogin.Type = Enums.CipherType.Login; + + return existingLogin; + } } public class LoginWithIdRequestModel : LoginRequestModel diff --git a/src/Core/Models/Api/Response/LoginResponseModel.cs b/src/Core/Models/Api/Response/LoginResponseModel.cs index c91f6e20b8..5020a37c29 100644 --- a/src/Core/Models/Api/Response/LoginResponseModel.cs +++ b/src/Core/Models/Api/Response/LoginResponseModel.cs @@ -1,11 +1,12 @@ using System; using Core.Models.Data; +using Bit.Core.Models.Table; namespace Bit.Core.Models.Api { public class LoginResponseModel : ResponseModel { - public LoginResponseModel(CipherDetails cipher, string obj = "login") + public LoginResponseModel(Cipher cipher, string obj = "login") : base(obj) { if(cipher == null) @@ -22,8 +23,6 @@ namespace Bit.Core.Models.Api Id = cipher.Id.ToString(); OrganizationId = cipher.OrganizationId?.ToString(); - FolderId = cipher.FolderId?.ToString(); - Favorite = cipher.Favorite; Name = data.Name; Uri = data.Uri; Username = data.Username; @@ -32,6 +31,13 @@ namespace Bit.Core.Models.Api RevisionDate = cipher.RevisionDate; } + public LoginResponseModel(CipherDetails cipher, string obj = "login") + : this(cipher as Cipher, obj) + { + FolderId = cipher.FolderId?.ToString(); + Favorite = cipher.Favorite; + } + public string Id { get; set; } public string OrganizationId { get; set; } public string FolderId { get; set; } diff --git a/src/Core/Services/ICipherService.cs b/src/Core/Services/ICipherService.cs index e7a8aa43fd..bdfc896fb5 100644 --- a/src/Core/Services/ICipherService.cs +++ b/src/Core/Services/ICipherService.cs @@ -8,7 +8,8 @@ namespace Bit.Core.Services { public interface ICipherService { - Task SaveAsync(CipherDetails cipher, Guid savingUserId); + Task SaveAsync(Cipher cipher, Guid savingUserId, bool orgAdmin = false); + Task SaveDetailsAsync(CipherDetails cipher, Guid savingUserId); Task DeleteAsync(Cipher cipher, Guid deletingUserId, bool orgAdmin = false); Task SaveFolderAsync(Folder folder); Task DeleteFolderAsync(Folder folder); diff --git a/src/Core/Services/Implementations/CipherService.cs b/src/Core/Services/Implementations/CipherService.cs index 08e97e5c01..e62bf0e0cf 100644 --- a/src/Core/Services/Implementations/CipherService.cs +++ b/src/Core/Services/Implementations/CipherService.cs @@ -40,7 +40,31 @@ namespace Bit.Core.Services _pushService = pushService; } - public async Task SaveAsync(CipherDetails cipher, Guid savingUserId) + public async Task SaveAsync(Cipher cipher, Guid savingUserId, bool orgAdmin = false) + { + if(!orgAdmin && !(await UserCanEditAsync(cipher, savingUserId))) + { + throw new BadRequestException("You do not have permissions to edit this."); + } + + if(cipher.Id == default(Guid)) + { + await _cipherRepository.CreateAsync(cipher); + + // push + await _pushService.PushSyncCipherCreateAsync(cipher); + } + else + { + cipher.RevisionDate = DateTime.UtcNow; + await _cipherRepository.ReplaceAsync(cipher); + + // push + await _pushService.PushSyncCipherUpdateAsync(cipher); + } + } + + public async Task SaveDetailsAsync(CipherDetails cipher, Guid savingUserId) { if(!(await UserCanEditAsync(cipher, savingUserId))) {