From a6b14131ef805dd52c2cf9082b8bdcfa329d5685 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Wed, 6 Mar 2019 09:15:23 -0500 Subject: [PATCH] public collection apis --- .../Controllers/CollectionsController.cs | 122 ++++++++++++++++++ .../CollectionCreateUpdateRequestModel.cs | 21 +++ .../Response/CollectionResponseModel.cs | 44 +++++++ 3 files changed, 187 insertions(+) create mode 100644 src/Api/Public/Controllers/CollectionsController.cs create mode 100644 src/Core/Models/Api/Public/Request/CollectionCreateUpdateRequestModel.cs create mode 100644 src/Core/Models/Api/Public/Response/CollectionResponseModel.cs diff --git a/src/Api/Public/Controllers/CollectionsController.cs b/src/Api/Public/Controllers/CollectionsController.cs new file mode 100644 index 0000000000..da646f01d2 --- /dev/null +++ b/src/Api/Public/Controllers/CollectionsController.cs @@ -0,0 +1,122 @@ +using System; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Bit.Core; +using Bit.Core.Models.Api.Public; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Bit.Api.Public.Controllers +{ + [Route("public/collections")] + [Authorize("Organization")] + public class CollectionsController : Controller + { + private readonly ICollectionRepository _collectionRepository; + private readonly ICollectionService _collectionService; + private readonly CurrentContext _currentContext; + + public CollectionsController( + ICollectionRepository collectionRepository, + ICollectionService collectionService, + CurrentContext currentContext) + { + _collectionRepository = collectionRepository; + _collectionService = collectionService; + _currentContext = currentContext; + } + + /// + /// Retrieve a collection. + /// + /// + /// Retrieves the details of an existing collection. You need only supply the unique collection identifier + /// that was returned upon collection creation. + /// + /// The identifier of the collection to be retrieved. + [HttpGet("{id}")] + [ProducesResponseType(typeof(CollectionResponseModel), (int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.NotFound)] + public async Task Get(Guid id) + { + var collectionWithGroups = await _collectionRepository.GetByIdWithGroupsAsync(id); + var collection = collectionWithGroups?.Item1; + if(collection == null || collection.OrganizationId != _currentContext.OrganizationId) + { + return new NotFoundResult(); + } + var response = new CollectionResponseModel(collection, collectionWithGroups.Item2); + return new JsonResult(response); + } + + /// + /// List all collections. + /// + /// + /// Returns a list of your organization's collections. + /// Collection objects listed in this call do not include information about their associated groups. + /// + [HttpGet] + [ProducesResponseType(typeof(ListResponseModel), (int)HttpStatusCode.OK)] + public async Task List() + { + var collections = await _collectionRepository.GetManyByOrganizationIdAsync( + _currentContext.OrganizationId.Value); + // TODO: Get all CollectionGroup associations for the organization and marry them up here for the response. + var collectionResponses = collections.Select(c => new CollectionResponseModel(c, null)); + var response = new ListResponseModel(collectionResponses); + return new JsonResult(response); + } + + /// + /// Update a collection. + /// + /// + /// Updates the specified collection object. If a property is not provided, + /// the value of the existing property will be reset. + /// + /// The identifier of the collection to be updated. + /// The request model. + [HttpPut("{id}")] + [ProducesResponseType(typeof(CollectionResponseModel), (int)HttpStatusCode.OK)] + [ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)] + [ProducesResponseType((int)HttpStatusCode.NotFound)] + public async Task Put(Guid id, [FromBody]CollectionCreateUpdateRequestModel model) + { + var existingCollection = await _collectionRepository.GetByIdAsync(id); + if(existingCollection == null || existingCollection.OrganizationId != _currentContext.OrganizationId) + { + return new NotFoundResult(); + } + var updatedCollection = model.ToCollection(existingCollection); + var associations = model.Groups?.Select(c => c.ToSelectionReadOnly()); + await _collectionService.SaveAsync(updatedCollection, associations); + var response = new CollectionResponseModel(updatedCollection, associations); + return new JsonResult(response); + } + + /// + /// Delete a collection. + /// + /// + /// Permanently deletes a collection. This cannot be undone. + /// + /// The identifier of the collection to be deleted. + [HttpDelete("{id}")] + [ProducesResponseType((int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.NotFound)] + public async Task Delete(Guid id) + { + var collection = await _collectionRepository.GetByIdAsync(id); + if(collection == null || collection.OrganizationId != _currentContext.OrganizationId) + { + return new NotFoundResult(); + } + await _collectionRepository.DeleteAsync(collection); + return new OkResult(); + } + } +} diff --git a/src/Core/Models/Api/Public/Request/CollectionCreateUpdateRequestModel.cs b/src/Core/Models/Api/Public/Request/CollectionCreateUpdateRequestModel.cs new file mode 100644 index 0000000000..3d877108bf --- /dev/null +++ b/src/Core/Models/Api/Public/Request/CollectionCreateUpdateRequestModel.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using Bit.Core.Models.Table; + +namespace Bit.Core.Models.Api.Public +{ + public class CollectionCreateUpdateRequestModel : GroupBaseModel + { + /// + /// The associated groups that this collection is assigned to. + /// + public IEnumerable Groups { get; set; } + + public Collection ToCollection(Collection existingCollection) + { + // TODO + // existingCollection.ExternalId = ExternalId; + return existingCollection; + } + } +} diff --git a/src/Core/Models/Api/Public/Response/CollectionResponseModel.cs b/src/Core/Models/Api/Public/Response/CollectionResponseModel.cs new file mode 100644 index 0000000000..25155bc996 --- /dev/null +++ b/src/Core/Models/Api/Public/Response/CollectionResponseModel.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Bit.Core.Models.Data; +using Bit.Core.Models.Table; + +namespace Bit.Core.Models.Api.Public +{ + /// + /// A collection. + /// + public class CollectionResponseModel : IResponseModel + { + public CollectionResponseModel(Collection collection, IEnumerable groups) + { + if(collection == null) + { + throw new ArgumentNullException(nameof(collection)); + } + + Id = collection.Id; + // ExternalId = group.ExternalId; TODO: Add external is for referencing purposes + Groups = groups?.Select(c => new AssociationWithPermissionsResponseModel(c)); + } + + /// + /// String representing the object's type. Objects of the same type share the same properties. + /// + /// collection + [Required] + public string Object => "collection"; + /// + /// The collection's unique identifier. + /// + /// 539a36c5-e0d2-4cf9-979e-51ecf5cf6593 + [Required] + public Guid Id { get; set; } + /// + /// The associated groups that this collection is assigned to. + /// + public IEnumerable Groups { get; set; } + } +}