diff --git a/src/Api/Public/Controllers/PoliciesController.cs b/src/Api/Public/Controllers/PoliciesController.cs new file mode 100644 index 000000000..03e8fc62e --- /dev/null +++ b/src/Api/Public/Controllers/PoliciesController.cs @@ -0,0 +1,135 @@ +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/policies")] + [Authorize("Organization")] + public class PoliciesController : Controller + { + private readonly IPolicyRepository _policyRepository; + private readonly IPolicyService _policyService; + private readonly CurrentContext _currentContext; + + public PoliciesController( + IPolicyRepository policyRepository, + IPolicyService policyService, + CurrentContext currentContext) + { + _policyRepository = policyRepository; + _policyService = policyService; + _currentContext = currentContext; + } + + /// + /// Retrieve a policy. + /// + /// + /// Retrieves the details of an existing policy. You need only supply the unique group identifier + /// that was returned upon policy creation. + /// + /// The identifier of the policy to be retrieved. + [HttpGet("{id}")] + [ProducesResponseType(typeof(GroupResponseModel), (int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.NotFound)] + public async Task Get(Guid id) + { + var policy = await _policyRepository.GetByIdAsync(id); + if(policy == null || policy.OrganizationId != _currentContext.OrganizationId) + { + return new NotFoundResult(); + } + var response = new PolicyResponseModel(policy); + return new JsonResult(response); + } + + /// + /// List all policies. + /// + /// + /// Returns a list of your organization's policies. + /// + [HttpGet] + [ProducesResponseType(typeof(ListResponseModel), (int)HttpStatusCode.OK)] + public async Task List() + { + var policies = await _policyRepository.GetManyByOrganizationIdAsync(_currentContext.OrganizationId.Value); + var policyResponses = policies.Select(p => new PolicyResponseModel(p)); + var response = new ListResponseModel(policyResponses); + return new JsonResult(response); + } + + /// + /// Create a policy. + /// + /// + /// Creates a new policy object. + /// + /// The request model. + [HttpPost] + [ProducesResponseType(typeof(PolicyResponseModel), (int)HttpStatusCode.OK)] + [ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)] + public async Task Post([FromBody]PolicyCreateRequestModel model) + { + var policy = model.ToPolicy(_currentContext.OrganizationId.Value); + await _policyService.SaveAsync(policy); + var response = new PolicyResponseModel(policy); + return new JsonResult(response); + } + + /// + /// Update a policy. + /// + /// + /// Updates the specified policy object. If a property is not provided, + /// the value of the existing property will be reset. + /// + /// The identifier of the policy to be updated. + /// The request model. + [HttpPut("{id}")] + [ProducesResponseType(typeof(PolicyResponseModel), (int)HttpStatusCode.OK)] + [ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)] + [ProducesResponseType((int)HttpStatusCode.NotFound)] + public async Task Put(Guid id, [FromBody]PolicyUpdateRequestModel model) + { + var existingPolicy = await _policyRepository.GetByIdAsync(id); + if(existingPolicy == null || existingPolicy.OrganizationId != _currentContext.OrganizationId) + { + return new NotFoundResult(); + } + var updatedPolicy = model.ToPolicy(existingPolicy); + await _policyService.SaveAsync(updatedPolicy); + var response = new PolicyResponseModel(updatedPolicy); + return new JsonResult(response); + } + + /// + /// Delete a policy. + /// + /// + /// Permanently deletes a policy. This cannot be undone. + /// + /// The identifier of the policy to be deleted. + [HttpDelete("{id}")] + [ProducesResponseType((int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.NotFound)] + public async Task Delete(Guid id) + { + var policy = await _policyRepository.GetByIdAsync(id); + if(policy == null || policy.OrganizationId != _currentContext.OrganizationId) + { + return new NotFoundResult(); + } + await _policyRepository.DeleteAsync(policy); + return new OkResult(); + } + } +} diff --git a/src/Core/Models/Api/Public/PolicyBaseModel.cs b/src/Core/Models/Api/Public/PolicyBaseModel.cs new file mode 100644 index 000000000..f0bc8f301 --- /dev/null +++ b/src/Core/Models/Api/Public/PolicyBaseModel.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Bit.Core.Models.Api.Public +{ + public abstract class PolicyBaseModel + { + /// + /// Determines if this policy is enabled and enforced. + /// + [Required] + public bool? Enabled { get; set; } + /// + /// Data for the policy. + /// + [StringLength(300)] + public Dictionary Data { get; set; } + } +} diff --git a/src/Core/Models/Api/Public/Request/PolicyCreateRequestModel.cs b/src/Core/Models/Api/Public/Request/PolicyCreateRequestModel.cs new file mode 100644 index 000000000..bc90d1e52 --- /dev/null +++ b/src/Core/Models/Api/Public/Request/PolicyCreateRequestModel.cs @@ -0,0 +1,23 @@ +using System; +using System.ComponentModel.DataAnnotations; +using Bit.Core.Models.Table; + +namespace Bit.Core.Models.Api.Public +{ + public class PolicyCreateRequestModel : PolicyUpdateRequestModel + { + /// + /// The type of policy. + /// + [Required] + public Enums.PolicyType? Type { get; set; } + + public Policy ToPolicy(Guid orgId) + { + return ToPolicy(new Policy + { + OrganizationId = orgId + }); + } + } +} diff --git a/src/Core/Models/Api/Public/Request/PolicyUpdateRequestModel.cs b/src/Core/Models/Api/Public/Request/PolicyUpdateRequestModel.cs new file mode 100644 index 000000000..6d508de5e --- /dev/null +++ b/src/Core/Models/Api/Public/Request/PolicyUpdateRequestModel.cs @@ -0,0 +1,15 @@ +using Bit.Core.Models.Table; +using Newtonsoft.Json; + +namespace Bit.Core.Models.Api.Public +{ + public class PolicyUpdateRequestModel : PolicyBaseModel + { + public virtual Policy ToPolicy(Policy existingPolicy) + { + existingPolicy.Enabled = Enabled.GetValueOrDefault(); + existingPolicy.Data = Data != null ? JsonConvert.SerializeObject(Data) : null; + return existingPolicy; + } + } +} diff --git a/src/Core/Models/Api/Public/Response/PolicyResponseModel.cs b/src/Core/Models/Api/Public/Response/PolicyResponseModel.cs new file mode 100644 index 000000000..b849f1a79 --- /dev/null +++ b/src/Core/Models/Api/Public/Response/PolicyResponseModel.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Bit.Core.Models.Table; +using Newtonsoft.Json; + +namespace Bit.Core.Models.Api.Public +{ + /// + /// A policy. + /// + public class PolicyResponseModel : PolicyBaseModel, IResponseModel + { + public PolicyResponseModel(Policy policy) + { + if(policy == null) + { + throw new ArgumentNullException(nameof(policy)); + } + + Id = policy.Id; + Type = policy.Type; + Enabled = policy.Enabled; + if(!string.IsNullOrWhiteSpace(policy.Data)) + { + Data = JsonConvert.DeserializeObject>(policy.Data); + } + } + + /// + /// String representing the object's type. Objects of the same type share the same properties. + /// + /// policy + [Required] + public string Object => "policy"; + /// + /// The policy's unique identifier. + /// + /// 539a36c5-e0d2-4cf9-979e-51ecf5cf6593 + [Required] + public Guid Id { get; set; } + /// + /// The type of policy. + /// + [Required] + public Enums.PolicyType? Type { get; set; } + } +}