diff --git a/src/Api/Controllers/PoliciesController.cs b/src/Api/Controllers/PoliciesController.cs index aedabde907..dfed165952 100644 --- a/src/Api/Controllers/PoliciesController.cs +++ b/src/Api/Controllers/PoliciesController.cs @@ -8,6 +8,7 @@ using Bit.Core.Models.Api; using Bit.Core.Exceptions; using Bit.Core.Services; using Bit.Core; +using Bit.Core.Enums; namespace Bit.Api.Controllers { @@ -29,11 +30,16 @@ namespace Bit.Api.Controllers _currentContext = currentContext; } - [HttpGet("{id}")] - public async Task Get(string orgId, string id) + [HttpGet("{type}")] + public async Task Get(string orgId, int type) { - var policy = await _policyRepository.GetByIdAsync(new Guid(id)); - if(policy == null || !_currentContext.OrganizationAdmin(policy.OrganizationId)) + var orgIdGuid = new Guid(orgId); + if(!_currentContext.OrganizationAdmin(orgIdGuid)) + { + throw new NotFoundException(); + } + var policy = await _policyRepository.GetByOrganizationIdTypeAsync(orgIdGuid, (PolicyType)type); + if(policy == null) { throw new NotFoundException(); } @@ -55,45 +61,26 @@ namespace Bit.Api.Controllers return new ListResponseModel(responses); } - [HttpPost("")] - public async Task Post(string orgId, [FromBody]PolicyRequestModel model) + [HttpPut("{type}")] + public async Task Put(string orgId, int type, [FromBody]PolicyRequestModel model) { var orgIdGuid = new Guid(orgId); if(!_currentContext.OrganizationAdmin(orgIdGuid)) { throw new NotFoundException(); } + var policy = await _policyRepository.GetByOrganizationIdTypeAsync(new Guid(orgId), (PolicyType)type); + if(policy == null) + { + policy = model.ToPolicy(orgIdGuid); + } + else + { + policy = model.ToPolicy(policy); + } - var policy = model.ToPolicy(orgIdGuid); await _policyService.SaveAsync(policy); return new PolicyResponseModel(policy); } - - [HttpPut("{id}")] - [HttpPost("{id}")] - public async Task Put(string orgId, string id, [FromBody]PolicyRequestModel model) - { - var policy = await _policyRepository.GetByIdAsync(new Guid(id)); - if(policy == null || !_currentContext.OrganizationAdmin(policy.OrganizationId)) - { - throw new NotFoundException(); - } - - await _policyService.SaveAsync(model.ToPolicy(policy)); - return new PolicyResponseModel(policy); - } - - [HttpDelete("{id}")] - [HttpPost("{id}/delete")] - public async Task Delete(string orgId, string id) - { - var policy = await _policyRepository.GetByIdAsync(new Guid(id)); - if(policy == null || !_currentContext.OrganizationAdmin(policy.OrganizationId)) - { - throw new NotFoundException(); - } - - await _policyService.DeleteAsync(policy); - } } } diff --git a/src/Api/Public/Controllers/PoliciesController.cs b/src/Api/Public/Controllers/PoliciesController.cs index 03e8fc62e9..667bcdf591 100644 --- a/src/Api/Public/Controllers/PoliciesController.cs +++ b/src/Api/Public/Controllers/PoliciesController.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Net; using System.Threading.Tasks; using Bit.Core; +using Bit.Core.Enums; using Bit.Core.Models.Api.Public; using Bit.Core.Repositories; using Bit.Core.Services; @@ -33,17 +34,17 @@ namespace Bit.Api.Public.Controllers /// Retrieve a policy. /// /// - /// Retrieves the details of an existing policy. You need only supply the unique group identifier - /// that was returned upon policy creation. + /// Retrieves the details of a policy. /// - /// The identifier of the policy to be retrieved. - [HttpGet("{id}")] + /// The type of policy to be retrieved. + [HttpGet("{type}")] [ProducesResponseType(typeof(GroupResponseModel), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.NotFound)] - public async Task Get(Guid id) + public async Task Get(PolicyType type) { - var policy = await _policyRepository.GetByIdAsync(id); - if(policy == null || policy.OrganizationId != _currentContext.OrganizationId) + var policy = await _policyRepository.GetByOrganizationIdTypeAsync( + _currentContext.OrganizationId.Value, type); + if(policy == null) { return new NotFoundResult(); } @@ -67,69 +68,34 @@ namespace Bit.Api.Public.Controllers 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, + /// Updates the specified policy. If a property is not provided, /// the value of the existing property will be reset. /// - /// The identifier of the policy to be updated. + /// The type of 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) + public async Task Put(PolicyType type, [FromBody]PolicyUpdateRequestModel model) { - var existingPolicy = await _policyRepository.GetByIdAsync(id); - if(existingPolicy == null || existingPolicy.OrganizationId != _currentContext.OrganizationId) + var policy = await _policyRepository.GetByOrganizationIdTypeAsync( + _currentContext.OrganizationId.Value, type); + if(policy == null) { - return new NotFoundResult(); + policy = model.ToPolicy(_currentContext.OrganizationId.Value); } - var updatedPolicy = model.ToPolicy(existingPolicy); - await _policyService.SaveAsync(updatedPolicy); - var response = new PolicyResponseModel(updatedPolicy); + else + { + policy = model.ToPolicy(policy); + } + await _policyService.SaveAsync(policy); + var response = new PolicyResponseModel(policy); 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 index f0bc8f3016..baf76b1b69 100644 --- a/src/Core/Models/Api/Public/PolicyBaseModel.cs +++ b/src/Core/Models/Api/Public/PolicyBaseModel.cs @@ -13,7 +13,6 @@ namespace Bit.Core.Models.Api.Public /// /// 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 deleted file mode 100644 index bc90d1e52d..0000000000 --- a/src/Core/Models/Api/Public/Request/PolicyCreateRequestModel.cs +++ /dev/null @@ -1,23 +0,0 @@ -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 index 6d508de5eb..dfbb968896 100644 --- a/src/Core/Models/Api/Public/Request/PolicyUpdateRequestModel.cs +++ b/src/Core/Models/Api/Public/Request/PolicyUpdateRequestModel.cs @@ -1,10 +1,19 @@ -using Bit.Core.Models.Table; +using System; +using Bit.Core.Models.Table; using Newtonsoft.Json; namespace Bit.Core.Models.Api.Public { public class PolicyUpdateRequestModel : PolicyBaseModel { + public Policy ToPolicy(Guid orgId) + { + return ToPolicy(new Policy + { + OrganizationId = orgId + }); + } + public virtual Policy ToPolicy(Policy existingPolicy) { existingPolicy.Enabled = Enabled.GetValueOrDefault(); diff --git a/src/Core/Repositories/IPolicyRepository.cs b/src/Core/Repositories/IPolicyRepository.cs index c26a90df60..42b54c0b78 100644 --- a/src/Core/Repositories/IPolicyRepository.cs +++ b/src/Core/Repositories/IPolicyRepository.cs @@ -2,11 +2,13 @@ using Bit.Core.Models.Table; using System.Collections.Generic; using System.Threading.Tasks; +using Bit.Core.Enums; namespace Bit.Core.Repositories { public interface IPolicyRepository : IRepository { + Task GetByOrganizationIdTypeAsync(Guid organizationId, PolicyType type); Task> GetManyByOrganizationIdAsync(Guid organizationId); } } diff --git a/src/Core/Repositories/SqlServer/PolicyRepository.cs b/src/Core/Repositories/SqlServer/PolicyRepository.cs index 381b300937..fcbca41aeb 100644 --- a/src/Core/Repositories/SqlServer/PolicyRepository.cs +++ b/src/Core/Repositories/SqlServer/PolicyRepository.cs @@ -6,6 +6,7 @@ using System.Data.SqlClient; using System.Data; using Dapper; using System.Linq; +using Bit.Core.Enums; namespace Bit.Core.Repositories.SqlServer { @@ -18,6 +19,18 @@ namespace Bit.Core.Repositories.SqlServer public PolicyRepository(string connectionString, string readOnlyConnectionString) : base(connectionString, readOnlyConnectionString) { } + public async Task GetByOrganizationIdTypeAsync(Guid organizationId, PolicyType type) + { + using(var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.QueryAsync( + $"[{Schema}].[{Table}_ReadByOrganizationIdType]", + new { OrganizationId = organizationId, Type = (byte)type }, + commandType: CommandType.StoredProcedure); + + return results.SingleOrDefault(); + } + } public async Task> GetManyByOrganizationIdAsync(Guid organizationId) { diff --git a/src/Sql/Sql.sqlproj b/src/Sql/Sql.sqlproj index bdaba04a5b..6a8f2eac33 100644 --- a/src/Sql/Sql.sqlproj +++ b/src/Sql/Sql.sqlproj @@ -259,5 +259,6 @@ + \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/Policy_ReadByOrganizationIdType.sql b/src/Sql/dbo/Stored Procedures/Policy_ReadByOrganizationIdType.sql new file mode 100644 index 0000000000..12bd9a1b2d --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/Policy_ReadByOrganizationIdType.sql @@ -0,0 +1,15 @@ +CREATE PROCEDURE [dbo].[Policy_ReadByOrganizationIdType] + @OrganizationId UNIQUEIDENTIFIER, + @Type TINYINT +AS +BEGIN + SET NOCOUNT ON + + SELECT TOP 1 + * + FROM + [dbo].[PolicyView] + WHERE + [OrganizationId] = @OrganizationId + AND [Type] = @Type +END \ No newline at end of file diff --git a/src/Sql/dbo/Tables/Policy.sql b/src/Sql/dbo/Tables/Policy.sql index ba7cf44f5c..e8dc162311 100644 --- a/src/Sql/dbo/Tables/Policy.sql +++ b/src/Sql/dbo/Tables/Policy.sql @@ -2,7 +2,7 @@ [Id] UNIQUEIDENTIFIER NOT NULL, [OrganizationId] UNIQUEIDENTIFIER NOT NULL, [Type] TINYINT NOT NULL, - [Data] NVARCHAR (MAX) NOT NULL, + [Data] NVARCHAR (MAX) NULL, [Enabled] BIT NOT NULL, [CreationDate] DATETIME2 (7) NOT NULL, [RevisionDate] DATETIME2 (7) NOT NULL, @@ -12,6 +12,6 @@ GO -CREATE NONCLUSTERED INDEX [IX_Policy_OrganizationId_Enabled] - ON [dbo].[Policy]([OrganizationId] ASC, [Enabled] ASC); +CREATE UNIQUE NONCLUSTERED INDEX [IX_Policy_OrganizationId_Type] + ON [dbo].[Policy]([OrganizationId] ASC, [Type] ASC); diff --git a/util/Migrator/DbScripts/2020-01-15_00_PolicySetup.sql b/util/Migrator/DbScripts/2020-01-15_00_PolicySetup.sql index 93d5975d46..ef2348205b 100644 --- a/util/Migrator/DbScripts/2020-01-15_00_PolicySetup.sql +++ b/util/Migrator/DbScripts/2020-01-15_00_PolicySetup.sql @@ -4,7 +4,7 @@ BEGIN [Id] UNIQUEIDENTIFIER NOT NULL, [OrganizationId] UNIQUEIDENTIFIER NOT NULL, [Type] TINYINT NOT NULL, - [Data] NVARCHAR (MAX) NOT NULL, + [Data] NVARCHAR (MAX) NULL, [Enabled] BIT NOT NULL, [CreationDate] DATETIME2 (7) NOT NULL, [RevisionDate] DATETIME2 (7) NOT NULL, @@ -12,8 +12,8 @@ BEGIN CONSTRAINT [FK_Policy_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]) ON DELETE CASCADE ); - CREATE NONCLUSTERED INDEX [IX_Policy_OrganizationId_Enabled] - ON [dbo].[Policy]([OrganizationId] ASC, [Enabled] ASC); + CREATE UNIQUE NONCLUSTERED INDEX [IX_Policy_OrganizationId_Type] + ON [dbo].[Policy]([OrganizationId] ASC, [Type] ASC); END GO @@ -128,6 +128,29 @@ BEGIN END GO +IF OBJECT_ID('[dbo].[Policy_ReadByOrganizationIdType]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Policy_ReadByOrganizationIdType] +END +GO + +CREATE PROCEDURE [dbo].[Policy_ReadByOrganizationIdType] + @OrganizationId UNIQUEIDENTIFIER, + @Type TINYINT +AS +BEGIN + SET NOCOUNT ON + + SELECT TOP 1 + * + FROM + [dbo].[PolicyView] + WHERE + [OrganizationId] = @OrganizationId + AND [Type] = @Type +END +GO + IF OBJECT_ID('[dbo].[Policy_Update]') IS NOT NULL BEGIN DROP PROCEDURE [dbo].[Policy_Update]