diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index 7258a9a48..dd12b0726 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -46,7 +46,7 @@ - + diff --git a/src/Core/IdentityServer/ApiScopes.cs b/src/Core/IdentityServer/ApiScopes.cs new file mode 100644 index 000000000..76a362027 --- /dev/null +++ b/src/Core/IdentityServer/ApiScopes.cs @@ -0,0 +1,20 @@ +using IdentityServer4.Models; +using System.Collections.Generic; + +namespace Bit.Core.IdentityServer +{ + public class ApiScopes + { + public static IEnumerable GetApiScopes() + { + return new List + { + new ApiScope("api", "API Access"), + new ApiScope("api.push", "API Push Access"), + new ApiScope("api.licensing", "API Licensing Access"), + new ApiScope("api.organization", "API Organization Access"), + new ApiScope("internal", "Internal Access") + }; + } + } +} diff --git a/src/Core/IdentityServer/AuthorizationCodeStore.cs b/src/Core/IdentityServer/AuthorizationCodeStore.cs index 194c676e1..9ae7300e6 100644 --- a/src/Core/IdentityServer/AuthorizationCodeStore.cs +++ b/src/Core/IdentityServer/AuthorizationCodeStore.cs @@ -23,7 +23,8 @@ namespace Bit.Core.IdentityServer public Task StoreAuthorizationCodeAsync(AuthorizationCode code) { - return CreateItemAsync(code, code.ClientId, code.Subject.GetSubjectId(), code.CreationTime, code.Lifetime); + return CreateItemAsync(code, code.ClientId, code.Subject.GetSubjectId(), code.SessionId, + code.Description, code.CreationTime, code.Lifetime); } public Task GetAuthorizationCodeAsync(string code) diff --git a/src/Core/IdentityServer/ClientStore.cs b/src/Core/IdentityServer/ClientStore.cs index 6d596ebb1..2005cebf7 100644 --- a/src/Core/IdentityServer/ClientStore.cs +++ b/src/Core/IdentityServer/ClientStore.cs @@ -47,7 +47,7 @@ namespace Bit.Core.IdentityServer AllowedGrantTypes = GrantTypes.ClientCredentials, AccessTokenLifetime = 3600 * 24, Enabled = installation.Enabled, - Claims = new List { new Claim(JwtClaimTypes.Subject, installation.Id.ToString()) } + Claims = new List { new ClientClaim(JwtClaimTypes.Subject, installation.Id.ToString()) } }; } } @@ -70,7 +70,7 @@ namespace Bit.Core.IdentityServer AllowedGrantTypes = GrantTypes.ClientCredentials, AccessTokenLifetime = 3600 * 24, Enabled = true, - Claims = new List { new Claim(JwtClaimTypes.Subject, id) } + Claims = new List { new ClientClaim(JwtClaimTypes.Subject, id) } }; } } @@ -92,7 +92,7 @@ namespace Bit.Core.IdentityServer AllowedGrantTypes = GrantTypes.ClientCredentials, AccessTokenLifetime = 3600 * 1, Enabled = org.Enabled && org.UseApi, - Claims = new List { new Claim(JwtClaimTypes.Subject, org.Id.ToString()) } + Claims = new List { new ClientClaim(JwtClaimTypes.Subject, org.Id.ToString()) } }; } } diff --git a/src/Core/IdentityServer/PersistedGrantStore.cs b/src/Core/IdentityServer/PersistedGrantStore.cs index 20768f40c..8365a995e 100644 --- a/src/Core/IdentityServer/PersistedGrantStore.cs +++ b/src/Core/IdentityServer/PersistedGrantStore.cs @@ -1,8 +1,6 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Bit.Core.Models.Table; using Bit.Core.Repositories; using IdentityServer4.Models; using IdentityServer4.Stores; @@ -19,13 +17,6 @@ namespace Bit.Core.IdentityServer _grantRepository = grantRepository; } - public async Task> GetAllAsync(string subjectId) - { - var grants = await _grantRepository.GetManyAsync(subjectId); - var pGrants = grants.Select(g => ToPersistedGrant(g)); - return pGrants; - } - public async Task GetAsync(string key) { var grant = await _grantRepository.GetByKeyAsync(key); @@ -38,19 +29,22 @@ namespace Bit.Core.IdentityServer return pGrant; } - public async Task RemoveAllAsync(string subjectId, string clientId) + public async Task> GetAllAsync(PersistedGrantFilter filter) { - await _grantRepository.DeleteAsync(subjectId, clientId); + var grants = await _grantRepository.GetManyAsync(filter.SubjectId, filter.SessionId, + filter.ClientId, filter.Type); + var pGrants = grants.Select(g => ToPersistedGrant(g)); + return pGrants; } - public async Task RemoveAllAsync(string subjectId, string clientId, string type) + public async Task RemoveAllAsync(PersistedGrantFilter filter) { - await _grantRepository.DeleteAsync(subjectId, clientId, type); + await _grantRepository.DeleteManyAsync(filter.SubjectId, filter.SessionId, filter.ClientId, filter.Type); } public async Task RemoveAsync(string key) { - await _grantRepository.DeleteAsync(key); + await _grantRepository.DeleteByKeyAsync(key); } public async Task StoreAsync(PersistedGrant pGrant) @@ -59,30 +53,36 @@ namespace Bit.Core.IdentityServer await _grantRepository.SaveAsync(grant); } - private Grant ToGrant(PersistedGrant pGrant) + private Models.Table.Grant ToGrant(PersistedGrant pGrant) { - return new Grant + return new Models.Table.Grant { Key = pGrant.Key, Type = pGrant.Type, SubjectId = pGrant.SubjectId, + SessionId = pGrant.SessionId, ClientId = pGrant.ClientId, + Description = pGrant.Description, CreationDate = pGrant.CreationTime, ExpirationDate = pGrant.Expiration, + ConsumedDate = pGrant.ConsumedTime, Data = pGrant.Data }; } - private PersistedGrant ToPersistedGrant(Grant grant) + private PersistedGrant ToPersistedGrant(Models.Table.Grant grant) { return new PersistedGrant { Key = grant.Key, Type = grant.Type, SubjectId = grant.SubjectId, + SessionId = grant.SessionId, ClientId = grant.ClientId, + Description = grant.Description, CreationTime = grant.CreationDate, Expiration = grant.ExpirationDate, + ConsumedTime = grant.ConsumedDate, Data = grant.Data }; } diff --git a/src/Core/IdentityServer/ProfileService.cs b/src/Core/IdentityServer/ProfileService.cs index 3801414b5..4d2affd36 100644 --- a/src/Core/IdentityServer/ProfileService.cs +++ b/src/Core/IdentityServer/ProfileService.cs @@ -14,19 +14,16 @@ namespace Bit.Core.IdentityServer public class ProfileService : IProfileService { private readonly IUserService _userService; - private readonly IUserRepository _userRepository; private readonly IOrganizationUserRepository _organizationUserRepository; private readonly ILicensingService _licensingService; private readonly CurrentContext _currentContext; public ProfileService( - IUserRepository userRepository, IUserService userService, IOrganizationUserRepository organizationUserRepository, ILicensingService licensingService, CurrentContext currentContext) { - _userRepository = userRepository; _userService = userService; _organizationUserRepository = organizationUserRepository; _licensingService = licensingService; @@ -46,7 +43,8 @@ namespace Bit.Core.IdentityServer { new Claim("premium", isPremium ? "true" : "false", ClaimValueTypes.Boolean), new Claim(JwtClaimTypes.Email, user.Email), - new Claim(JwtClaimTypes.EmailVerified, user.EmailVerified ? "true" : "false", ClaimValueTypes.Boolean), + new Claim(JwtClaimTypes.EmailVerified, user.EmailVerified ? "true" : "false", + ClaimValueTypes.Boolean), new Claim("sstamp", user.SecurityStamp) }); @@ -96,13 +94,14 @@ namespace Bit.Core.IdentityServer // filter out any of the new claims var existingClaimsToKeep = existingClaims - .Where(c => !c.Type.StartsWith("org") && (newClaims.Count == 0 || !newClaims.Any(nc => nc.Type == c.Type))) + .Where(c => !c.Type.StartsWith("org") && + (newClaims.Count == 0 || !newClaims.Any(nc => nc.Type == c.Type))) .ToList(); newClaims.AddRange(existingClaimsToKeep); if (newClaims.Any()) { - context.AddRequestedClaims(newClaims); + context.IssuedClaims.AddRange(newClaims); } } diff --git a/src/Core/Models/Table/Grant.cs b/src/Core/Models/Table/Grant.cs index 95fd16957..785fa5d1a 100644 --- a/src/Core/Models/Table/Grant.cs +++ b/src/Core/Models/Table/Grant.cs @@ -7,9 +7,12 @@ namespace Bit.Core.Models.Table public string Key { get; set; } public string Type { get; set; } public string SubjectId { get; set; } + public string SessionId { get; set; } public string ClientId { get; set; } + public string Description { get; set; } public DateTime CreationDate { get; set; } public DateTime? ExpirationDate { get; set; } + public DateTime? ConsumedDate { get; set; } public string Data { get; set; } } } diff --git a/src/Core/Repositories/IGrantRepository.cs b/src/Core/Repositories/IGrantRepository.cs index ba86c6b28..86730040a 100644 --- a/src/Core/Repositories/IGrantRepository.cs +++ b/src/Core/Repositories/IGrantRepository.cs @@ -8,10 +8,9 @@ namespace Bit.Core.Repositories public interface IGrantRepository { Task GetByKeyAsync(string key); - Task> GetManyAsync(string subjectId); + Task> GetManyAsync(string subjectId, string sessionId, string clientId, string type); Task SaveAsync(Grant obj); - Task DeleteAsync(string key); - Task DeleteAsync(string subjectId, string clientId); - Task DeleteAsync(string subjectId, string clientId, string type); + Task DeleteByKeyAsync(string key); + Task DeleteManyAsync(string subjectId, string sessionId, string clientId, string type); } } diff --git a/src/Core/Repositories/SqlServer/GrantRepository.cs b/src/Core/Repositories/SqlServer/GrantRepository.cs index 39fe540ff..39b71bb67 100644 --- a/src/Core/Repositories/SqlServer/GrantRepository.cs +++ b/src/Core/Repositories/SqlServer/GrantRepository.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Data; using System.Data.SqlClient; using System.Linq; @@ -32,13 +31,14 @@ namespace Bit.Core.Repositories.SqlServer } } - public async Task> GetManyAsync(string subjectId) + public async Task> GetManyAsync(string subjectId, string sessionId, + string clientId, string type) { using (var connection = new SqlConnection(ConnectionString)) { var results = await connection.QueryAsync( - "[dbo].[Grant_ReadBySubjectId]", - new { SubjectId = subjectId }, + "[dbo].[Grant_Read]", + new { SubjectId = subjectId, SessionId = sessionId, ClientId = clientId, Type = type }, commandType: CommandType.StoredProcedure); return results.ToList(); @@ -56,7 +56,7 @@ namespace Bit.Core.Repositories.SqlServer } } - public async Task DeleteAsync(string key) + public async Task DeleteByKeyAsync(string key) { using (var connection = new SqlConnection(ConnectionString)) { @@ -67,24 +67,13 @@ namespace Bit.Core.Repositories.SqlServer } } - public async Task DeleteAsync(string subjectId, string clientId) + public async Task DeleteManyAsync(string subjectId, string sessionId, string clientId, string type) { using (var connection = new SqlConnection(ConnectionString)) { await connection.ExecuteAsync( - "[dbo].[Grant_DeleteBySubjectIdClientId]", - new { SubjectId = subjectId, ClientId = clientId }, - commandType: CommandType.StoredProcedure); - } - } - - public async Task DeleteAsync(string subjectId, string clientId, string type) - { - using (var connection = new SqlConnection(ConnectionString)) - { - await connection.ExecuteAsync( - "[dbo].[Grant_DeleteBySubjectIdClientIdType]", - new { SubjectId = subjectId, ClientId = clientId, Type = type }, + "[dbo].[Grant_Delete]", + new { SubjectId = subjectId, SessionId = sessionId, ClientId = clientId, Type = type }, commandType: CommandType.StoredProcedure); } } diff --git a/src/Identity/Controllers/AccountController.cs b/src/Identity/Controllers/AccountController.cs index 2b81b3c4d..b89856470 100644 --- a/src/Identity/Controllers/AccountController.cs +++ b/src/Identity/Controllers/AccountController.cs @@ -7,6 +7,7 @@ using Bit.Core.Models.Table; using Bit.Core.Repositories; using Bit.Identity.Models; using IdentityModel; +using IdentityServer4; using IdentityServer4.Services; using IdentityServer4.Stores; using Microsoft.AspNetCore.Authentication; @@ -132,8 +133,12 @@ namespace Bit.Identity.Controllers ProcessLoginCallbackForOidc(result, additionalLocalClaims, localSignInProps); // issue authentication cookie for user - await HttpContext.SignInAsync(user.Id.ToString(), user.Email, provider, - localSignInProps, additionalLocalClaims.ToArray()); + await HttpContext.SignInAsync(new IdentityServerUser(user.Id.ToString()) + { + DisplayName = user.Email, + IdentityProvider = provider, + AdditionalClaims = additionalLocalClaims.ToArray() + }, localSignInProps); // delete temporary cookie used during external authentication await HttpContext.SignOutAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme); @@ -144,7 +149,7 @@ namespace Bit.Identity.Controllers var context = await _interaction.GetAuthorizationContextAsync(returnUrl); if (context != null) { - if (await IsPkceClientAsync(context.ClientId)) + if (await IsPkceClientAsync(context.Client.ClientId)) { // if the client is PKCE then we assume it's native, so this change in how to // return the response is for better UX for the end user. diff --git a/src/Identity/Startup.cs b/src/Identity/Startup.cs index d4ac92be3..119a0ac40 100644 --- a/src/Identity/Startup.cs +++ b/src/Identity/Startup.cs @@ -65,6 +65,19 @@ namespace Bit.Identity services.AddSingleton(); } + // Cookies + if (Environment.IsDevelopment()) + { + services.Configure(options => + { + options.MinimumSameSitePolicy = Microsoft.AspNetCore.Http.SameSiteMode.Unspecified; + options.OnAppendCookie = ctx => + { + ctx.CookieOptions.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Unspecified; + }; + }); + } + JwtSecurityTokenHandler.DefaultMapInboundClaims = false; // Authentication @@ -133,6 +146,12 @@ namespace Bit.Identity app.UseForwardedHeaders(globalSettings); } + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + app.UseCookiePolicy(); + } + // Add static files to the request pipeline. app.UseStaticFiles(); @@ -172,9 +191,14 @@ namespace Bit.Identity options.Endpoints.EnableTokenRevocationEndpoint = false; options.IssuerUri = $"{issuerUri.Scheme}://{issuerUri.Host}"; options.Caching.ClientStoreExpiration = new TimeSpan(0, 5, 0); + if(env.IsDevelopment()) + { + options.Authentication.CookieSameSiteMode = Microsoft.AspNetCore.Http.SameSiteMode.Unspecified; + } }) .AddInMemoryCaching() .AddInMemoryApiResources(ApiResources.GetApiResources()) + .AddInMemoryApiScopes(ApiScopes.GetApiScopes()) .AddClientStoreCache() .AddCustomTokenRequestValidator() .AddProfileService() diff --git a/src/Sql/Sql.sqlproj b/src/Sql/Sql.sqlproj index ee4fd6978..afc9cf443 100644 --- a/src/Sql/Sql.sqlproj +++ b/src/Sql/Sql.sqlproj @@ -109,12 +109,11 @@ - + - - + diff --git a/src/Sql/dbo/Stored Procedures/Grant_Delete.sql b/src/Sql/dbo/Stored Procedures/Grant_Delete.sql new file mode 100644 index 000000000..59a2d1882 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/Grant_Delete.sql @@ -0,0 +1,18 @@ +CREATE PROCEDURE [dbo].[Grant_Delete] + @SubjectId NVARCHAR(200), + @SessionId NVARCHAR(100), + @ClientId NVARCHAR(200), + @Type NVARCHAR(50) +AS +BEGIN + SET NOCOUNT ON + + DELETE + FROM + [dbo].[Grant] + WHERE + (@SubjectId IS NULL OR [SubjectId] = @SubjectId) + AND (@ClientId IS NULL OR [ClientId] = @ClientId) + AND (@SessionId IS NULL OR [SessionId] = @SessionId) + AND (@Type IS NULL OR [Type] = @Type) +END diff --git a/src/Sql/dbo/Stored Procedures/Grant_DeleteBySubjectIdClientId.sql b/src/Sql/dbo/Stored Procedures/Grant_DeleteBySubjectIdClientId.sql deleted file mode 100644 index 48d5036cb..000000000 --- a/src/Sql/dbo/Stored Procedures/Grant_DeleteBySubjectIdClientId.sql +++ /dev/null @@ -1,14 +0,0 @@ -CREATE PROCEDURE [dbo].[Grant_DeleteBySubjectIdClientId] - @SubjectId NVARCHAR(50), - @ClientId NVARCHAR(50) -AS -BEGIN - SET NOCOUNT ON - - DELETE - FROM - [dbo].[Grant] - WHERE - [SubjectId] = @SubjectId - AND [ClientId] = @ClientId -END diff --git a/src/Sql/dbo/Stored Procedures/Grant_DeleteBySubjectIdClientIdType.sql b/src/Sql/dbo/Stored Procedures/Grant_DeleteBySubjectIdClientIdType.sql deleted file mode 100644 index 5f7c48373..000000000 --- a/src/Sql/dbo/Stored Procedures/Grant_DeleteBySubjectIdClientIdType.sql +++ /dev/null @@ -1,16 +0,0 @@ -CREATE PROCEDURE [dbo].[Grant_DeleteBySubjectIdClientIdType] - @SubjectId NVARCHAR(50), - @ClientId NVARCHAR(50), - @Type NVARCHAR(50) -AS -BEGIN - SET NOCOUNT ON - - DELETE - FROM - [dbo].[Grant] - WHERE - [SubjectId] = @SubjectId - AND [ClientId] = @ClientId - AND [Type] = @Type -END diff --git a/src/Sql/dbo/Stored Procedures/Grant_Read.sql b/src/Sql/dbo/Stored Procedures/Grant_Read.sql new file mode 100644 index 000000000..9cbd617ce --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/Grant_Read.sql @@ -0,0 +1,19 @@ +CREATE PROCEDURE [dbo].[Grant_Read] + @SubjectId NVARCHAR(200), + @SessionId NVARCHAR(100), + @ClientId NVARCHAR(200), + @Type NVARCHAR(50) +AS +BEGIN + SET NOCOUNT ON + + SELECT + * + FROM + [dbo].[GrantView] + WHERE + (@SubjectId IS NULL OR [SubjectId] = @SubjectId) + AND (@ClientId IS NULL OR [ClientId] = @ClientId) + AND (@SessionId IS NULL OR [SessionId] = @SessionId) + AND (@Type IS NULL OR [Type] = @Type) +END diff --git a/src/Sql/dbo/Stored Procedures/Grant_ReadBySubjectId.sql b/src/Sql/dbo/Stored Procedures/Grant_ReadBySubjectId.sql deleted file mode 100644 index f4270b2e6..000000000 --- a/src/Sql/dbo/Stored Procedures/Grant_ReadBySubjectId.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE PROCEDURE [dbo].[Grant_ReadBySubjectId] - @SubjectId NVARCHAR(50) -AS -BEGIN - SET NOCOUNT ON - - SELECT - * - FROM - [dbo].[GrantView] - WHERE - [SubjectId] = @SubjectId -END diff --git a/src/Sql/dbo/Stored Procedures/Grant_Save.sql b/src/Sql/dbo/Stored Procedures/Grant_Save.sql index 097fd6c25..bac37f140 100644 --- a/src/Sql/dbo/Stored Procedures/Grant_Save.sql +++ b/src/Sql/dbo/Stored Procedures/Grant_Save.sql @@ -1,10 +1,13 @@ CREATE PROCEDURE [dbo].[Grant_Save] @Key NVARCHAR(200), @Type NVARCHAR(50), - @SubjectId NVARCHAR(50), - @ClientId NVARCHAR(50), + @SubjectId NVARCHAR(200), + @SessionId NVARCHAR(100), + @ClientId NVARCHAR(200), + @Description NVARCHAR(200), @CreationDate DATETIME2, @ExpirationDate DATETIME2, + @ConsumedDate DATETIME2, @Data NVARCHAR(MAX) AS BEGIN @@ -19,9 +22,12 @@ BEGIN @Key, @Type, @SubjectId, + @SessionId, @ClientId, + @Description, @CreationDate, @ExpirationDate, + @ConsumedDate, @Data ) ) AS [Source] @@ -29,9 +35,12 @@ BEGIN [Key], [Type], [SubjectId], + [SessionId], [ClientId], + [Description], [CreationDate], [ExpirationDate], + [ConsumedDate], [Data] ) ON @@ -41,9 +50,12 @@ BEGIN SET [Type] = [Source].[Type], [SubjectId] = [Source].[SubjectId], + [SessionId] = [Source].[SessionId], [ClientId] = [Source].[ClientId], + [Description] = [Source].[Description], [CreationDate] = [Source].[CreationDate], [ExpirationDate] = [Source].[ExpirationDate], + [ConsumedDate] = [Source].[ConsumedDate], [Data] = [Source].[Data] WHEN NOT MATCHED THEN INSERT @@ -51,9 +63,12 @@ BEGIN [Key], [Type], [SubjectId], + [SessionId], [ClientId], + [Description], [CreationDate], [ExpirationDate], + [ConsumedDate], [Data] ) VALUES @@ -61,9 +76,12 @@ BEGIN [Source].[Key], [Source].[Type], [Source].[SubjectId], + [Source].[SessionId], [Source].[ClientId], + [Source].[Description], [Source].[CreationDate], [Source].[ExpirationDate], + [Source].[ConsumedDate], [Source].[Data] ) ; diff --git a/src/Sql/dbo/Tables/Grant.sql b/src/Sql/dbo/Tables/Grant.sql index 6ec355229..02eda444b 100644 --- a/src/Sql/dbo/Tables/Grant.sql +++ b/src/Sql/dbo/Tables/Grant.sql @@ -1,10 +1,13 @@ CREATE TABLE [dbo].[Grant] ( [Key] NVARCHAR (200) NOT NULL, - [Type] NVARCHAR (50) NULL, - [SubjectId] NVARCHAR (50) NULL, - [ClientId] NVARCHAR (50) NOT NULL, + [Type] NVARCHAR (50) NOT NULL, + [SubjectId] NVARCHAR (200) NULL, + [SessionId] NVARCHAR (100) NULL, + [ClientId] NVARCHAR (200) NOT NULL, + [Description] NVARCHAR (200) NULL, [CreationDate] DATETIME2 (7) NOT NULL, [ExpirationDate] DATETIME2 (7) NULL, + [ConsumedDate] DATETIME2 (7) NULL, [Data] NVARCHAR (MAX) NOT NULL, CONSTRAINT [PK_Grant] PRIMARY KEY CLUSTERED ([Key] ASC) ); @@ -14,6 +17,10 @@ GO CREATE NONCLUSTERED INDEX [IX_Grant_SubjectId_ClientId_Type] ON [dbo].[Grant]([SubjectId] ASC, [ClientId] ASC, [Type] ASC); +GO +CREATE NONCLUSTERED INDEX [IX_Grant_SubjectId_SessionId_Type] + ON [dbo].[Grant]([SubjectId] ASC, [SessionId] ASC, [Type] ASC); + GO CREATE NONCLUSTERED INDEX [IX_Grant_ExpirationDate] ON [dbo].[Grant]([ExpirationDate] ASC); diff --git a/util/Migrator/DbScripts/2020-07-30_00_IdServerv4.sql b/util/Migrator/DbScripts/2020-07-30_00_IdServerv4.sql new file mode 100644 index 000000000..265638347 --- /dev/null +++ b/util/Migrator/DbScripts/2020-07-30_00_IdServerv4.sql @@ -0,0 +1,267 @@ +IF EXISTS ( + SELECT * FROM sys.indexes WHERE [Name] = 'IX_Grant_SubjectId_ClientId_Type' + AND object_id = OBJECT_ID('[dbo].[Grant]') +) +BEGIN + DROP INDEX [IX_Grant_SubjectId_ClientId_Type] + ON [dbo].[Grant] +END +GO + +IF EXISTS ( + SELECT * FROM sys.indexes WHERE [Name] = 'IX_Grant_SubjectId_SessionId_Type' + AND object_id = OBJECT_ID('[dbo].[Grant]') +) +BEGIN + DROP INDEX [IX_Grant_SubjectId_SessionId_Type] + ON [dbo].[Grant] +END +GO + +IF COL_LENGTH('[dbo].[Grant]', 'SessionId') IS NULL +BEGIN + ALTER TABLE + [dbo].[Grant] + ADD + [SessionId] NVARCHAR (100) NULL +END +GO + +IF COL_LENGTH('[dbo].[Grant]', 'Description') IS NULL +BEGIN + ALTER TABLE + [dbo].[Grant] + ADD + [Description] NVARCHAR (200) NULL +END +GO + +IF COL_LENGTH('[dbo].[Grant]', 'ConsumedDate') IS NULL +BEGIN + ALTER TABLE + [dbo].[Grant] + ADD + [ConsumedDate] DATETIME2 (7) NULL +END +GO + +ALTER TABLE + [dbo].[Grant] +ALTER COLUMN + [Type] NVARCHAR (50) NOT NULL +GO + +ALTER TABLE + [dbo].[Grant] +ALTER COLUMN + [SubjectId] NVARCHAR (200) NULL +GO + +ALTER TABLE + [dbo].[Grant] +ALTER COLUMN + [ClientId] NVARCHAR (200) NOT NULL +GO + +IF EXISTS(SELECT * FROM sys.views WHERE [Name] = 'GrantView') +BEGIN + DROP VIEW [dbo].[GrantView] +END +GO + +CREATE VIEW [dbo].[GrantView] +AS +SELECT + * +FROM + [dbo].[Grant] +GO + +IF NOT EXISTS ( + SELECT * FROM sys.indexes WHERE [Name] = 'IX_Grant_SubjectId_ClientId_Type' + AND object_id = OBJECT_ID('[dbo].[Grant]') +) +BEGIN + CREATE NONCLUSTERED INDEX [IX_Grant_SubjectId_ClientId_Type] + ON [dbo].[Grant]([SubjectId] ASC, [ClientId] ASC, [Type] ASC) + -- TODO WITH ONLINE +END +GO + +IF NOT EXISTS ( + SELECT * FROM sys.indexes WHERE [Name] = 'IX_Grant_SubjectId_SessionId_Type' + AND object_id = OBJECT_ID('[dbo].[Grant]') +) +BEGIN + CREATE NONCLUSTERED INDEX [IX_Grant_SubjectId_SessionId_Type] + ON [dbo].[Grant]([SubjectId] ASC, [SessionId] ASC, [Type] ASC) + -- TODO WITH ONLINE +END +GO + +IF OBJECT_ID('[dbo].[Grant_Delete]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Grant_Delete] +END +GO + +CREATE PROCEDURE [dbo].[Grant_Delete] + @SubjectId NVARCHAR(200), + @SessionId NVARCHAR(100), + @ClientId NVARCHAR(200), + @Type NVARCHAR(50) +AS +BEGIN + SET NOCOUNT ON + + DELETE + FROM + [dbo].[Grant] + WHERE + (@SubjectId IS NULL OR [SubjectId] = @SubjectId) + AND (@ClientId IS NULL OR [ClientId] = @ClientId) + AND (@SessionId IS NULL OR [SessionId] = @SessionId) + AND (@Type IS NULL OR [Type] = @Type) +END +GO + +IF OBJECT_ID('[dbo].[Grant_Read]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Grant_Read] +END +GO + +CREATE PROCEDURE [dbo].[Grant_Read] + @SubjectId NVARCHAR(200), + @SessionId NVARCHAR(100), + @ClientId NVARCHAR(200), + @Type NVARCHAR(50) +AS +BEGIN + SET NOCOUNT ON + + SELECT + * + FROM + [dbo].[GrantView] + WHERE + (@SubjectId IS NULL OR [SubjectId] = @SubjectId) + AND (@ClientId IS NULL OR [ClientId] = @ClientId) + AND (@SessionId IS NULL OR [SessionId] = @SessionId) + AND (@Type IS NULL OR [Type] = @Type) +END +GO + +IF OBJECT_ID('[dbo].[Grant_Save]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Grant_Save] +END +GO + +CREATE PROCEDURE [dbo].[Grant_Save] + @Key NVARCHAR(200), + @Type NVARCHAR(50), + @SubjectId NVARCHAR(200), + @SessionId NVARCHAR(100), + @ClientId NVARCHAR(200), + @Description NVARCHAR(200), + @CreationDate DATETIME2, + @ExpirationDate DATETIME2, + @ConsumedDate DATETIME2, + @Data NVARCHAR(MAX) +AS +BEGIN + SET NOCOUNT ON + + MERGE + [dbo].[Grant] AS [Target] + USING + ( + VALUES + ( + @Key, + @Type, + @SubjectId, + @SessionId, + @ClientId, + @Description, + @CreationDate, + @ExpirationDate, + @ConsumedDate, + @Data + ) + ) AS [Source] + ( + [Key], + [Type], + [SubjectId], + [SessionId], + [ClientId], + [Description], + [CreationDate], + [ExpirationDate], + [ConsumedDate], + [Data] + ) + ON + [Target].[Key] = [Source].[Key] + WHEN MATCHED THEN + UPDATE + SET + [Type] = [Source].[Type], + [SubjectId] = [Source].[SubjectId], + [SessionId] = [Source].[SessionId], + [ClientId] = [Source].[ClientId], + [Description] = [Source].[Description], + [CreationDate] = [Source].[CreationDate], + [ExpirationDate] = [Source].[ExpirationDate], + [ConsumedDate] = [Source].[ConsumedDate], + [Data] = [Source].[Data] + WHEN NOT MATCHED THEN + INSERT + ( + [Key], + [Type], + [SubjectId], + [SessionId], + [ClientId], + [Description], + [CreationDate], + [ExpirationDate], + [ConsumedDate], + [Data] + ) + VALUES + ( + [Source].[Key], + [Source].[Type], + [Source].[SubjectId], + [Source].[SessionId], + [Source].[ClientId], + [Source].[Description], + [Source].[CreationDate], + [Source].[ExpirationDate], + [Source].[ConsumedDate], + [Source].[Data] + ) + ; +END +GO + +IF OBJECT_ID('[dbo].[Grant_DeleteBySubjectIdClientId]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Grant_DeleteBySubjectIdClientId] +END +GO + +IF OBJECT_ID('[dbo].[Grant_DeleteBySubjectIdClientIdType]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Grant_DeleteBySubjectIdClientIdType] +END +GO + +IF OBJECT_ID('[dbo].[Grant_ReadBySubjectId]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Grant_ReadBySubjectId] +END +GO