diff --git a/src/OpenIddict.Core/Descriptors/OpenIddictAuthorizationDescriptor.cs b/src/OpenIddict.Core/Descriptors/OpenIddictAuthorizationDescriptor.cs index 04b129ec..292de19e 100644 --- a/src/OpenIddict.Core/Descriptors/OpenIddictAuthorizationDescriptor.cs +++ b/src/OpenIddict.Core/Descriptors/OpenIddictAuthorizationDescriptor.cs @@ -28,5 +28,10 @@ namespace OpenIddict.Core /// Gets or sets the subject associated with the authorization. /// public string Subject { get; set; } + + /// + /// Gets or sets the type of the authorization. + /// + public virtual string Type { get; set; } } } diff --git a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs index 945e1614..82ed13ed 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs @@ -85,6 +85,12 @@ namespace OpenIddict.Core throw new ArgumentNullException(nameof(authorization)); } + // If no type was explicitly specified, assume that the authorization is a permanent authorization. + if (string.IsNullOrEmpty(await Store.GetTypeAsync(authorization, cancellationToken))) + { + await Store.SetTypeAsync(authorization, OpenIddictConstants.AuthorizationTypes.Permanent, cancellationToken); + } + await ValidateAsync(authorization, cancellationToken); return await Store.CreateAsync(authorization, cancellationToken); } @@ -104,6 +110,13 @@ namespace OpenIddict.Core throw new ArgumentNullException(nameof(descriptor)); } + // If no type was explicitly specified, assume that + // the authorization is a permanent authorization. + if (string.IsNullOrEmpty(descriptor.Type)) + { + descriptor.Type = OpenIddictConstants.AuthorizationTypes.Permanent; + } + await ValidateAsync(descriptor, cancellationToken); return await Store.CreateAsync(descriptor, cancellationToken); } @@ -309,6 +322,17 @@ namespace OpenIddict.Core throw new ArgumentNullException(nameof(descriptor)); } + if (string.IsNullOrEmpty(descriptor.Type)) + { + throw new ArgumentException("The authorization type cannot be null or empty.", nameof(descriptor)); + } + + if (!string.Equals(descriptor.Type, OpenIddictConstants.AuthorizationTypes.AdHoc, StringComparison.OrdinalIgnoreCase) && + !string.Equals(descriptor.Type, OpenIddictConstants.AuthorizationTypes.Permanent, StringComparison.OrdinalIgnoreCase)) + { + throw new ArgumentException("The specified authorization type is not supported by the default token manager."); + } + if (string.IsNullOrEmpty(descriptor.Status)) { throw new ArgumentException("The status cannot be null or empty."); diff --git a/src/OpenIddict.Core/OpenIddictConstants.cs b/src/OpenIddict.Core/OpenIddictConstants.cs index 4ba722e7..dce4eb79 100644 --- a/src/OpenIddict.Core/OpenIddictConstants.cs +++ b/src/OpenIddict.Core/OpenIddictConstants.cs @@ -8,6 +8,12 @@ namespace OpenIddict.Core { public static class OpenIddictConstants { + public static class AuthorizationTypes + { + public const string AdHoc = "ad-hoc"; + public const string Permanent = "permanent"; + } + public static class Claims { public const string Roles = "roles"; diff --git a/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs index 1449c97f..b1c478de 100644 --- a/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs @@ -138,6 +138,17 @@ namespace OpenIddict.Core /// Task GetSubjectAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken); + /// + /// Retrieves the type associated with an authorization. + /// + /// The authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the type associated with the specified authorization. + /// + Task GetTypeAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken); + /// /// Executes the specified query. /// @@ -173,6 +184,17 @@ namespace OpenIddict.Core /// Task SetStatusAsync([NotNull] TAuthorization authorization, [NotNull] string status, CancellationToken cancellationToken); + /// + /// Sets the type associated with an authorization. + /// + /// The authorization. + /// The type associated with the authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + Task SetTypeAsync([NotNull] TAuthorization authorization, [NotNull] string type, CancellationToken cancellationToken); + /// /// Updates an existing authorization. /// diff --git a/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs index 5af2732f..a9474cf7 100644 --- a/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs @@ -199,6 +199,25 @@ namespace OpenIddict.Core return Task.FromResult(authorization.Subject); } + /// + /// Retrieves the type associated with an authorization. + /// + /// The authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the type associated with the specified authorization. + /// + public virtual Task GetTypeAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization == null) + { + throw new ArgumentNullException(nameof(authorization)); + } + + return Task.FromResult(authorization.Type); + } + /// /// Executes the specified query. /// @@ -254,11 +273,37 @@ namespace OpenIddict.Core /// public virtual Task SetStatusAsync([NotNull] TAuthorization authorization, [NotNull] string status, CancellationToken cancellationToken) { + if (authorization == null) + { + throw new ArgumentNullException(nameof(authorization)); + } + authorization.Status = status; return Task.CompletedTask; } + /// + /// Sets the type associated with an authorization. + /// + /// The authorization. + /// The type associated with the authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual Task SetTypeAsync([NotNull] TAuthorization authorization, [NotNull] string type, CancellationToken cancellationToken) + { + if (authorization == null) + { + throw new ArgumentNullException(nameof(authorization)); + } + + authorization.Type = type; + + return Task.FromResult(0); + } + /// /// Updates an existing authorization. /// diff --git a/src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs index a5f7aea5..65e7921a 100644 --- a/src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs +++ b/src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs @@ -432,6 +432,11 @@ namespace OpenIddict.Core public virtual Task SetExpirationDateAsync([NotNull] TToken token, [CanBeNull] DateTimeOffset? date, CancellationToken cancellationToken) { + if (token == null) + { + throw new ArgumentNullException(nameof(token)); + } + token.ExpirationDate = date; return Task.CompletedTask; @@ -448,6 +453,11 @@ namespace OpenIddict.Core /// public virtual Task SetStatusAsync([NotNull] TToken token, [NotNull] string status, CancellationToken cancellationToken) { + if (token == null) + { + throw new ArgumentNullException(nameof(token)); + } + token.Status = status; return Task.CompletedTask; diff --git a/src/OpenIddict.EntityFramework/OpenIddictExtensions.cs b/src/OpenIddict.EntityFramework/OpenIddictExtensions.cs index da835d5f..5289f4af 100644 --- a/src/OpenIddict.EntityFramework/OpenIddictExtensions.cs +++ b/src/OpenIddict.EntityFramework/OpenIddictExtensions.cs @@ -184,56 +184,85 @@ namespace Microsoft.Extensions.DependencyInjection // Configure the TApplication entity. builder.Entity() - .HasKey(application => application.Id); + .HasKey(application => application.Id); builder.Entity() - .Property(application => application.ClientId) - .HasMaxLength(450) - .HasColumnAnnotation(IndexAnnotation.AnnotationName, new IndexAnnotation(new IndexAttribute())); + .Property(application => application.ClientId) + .IsRequired() + .HasMaxLength(450) + .HasColumnAnnotation(IndexAnnotation.AnnotationName, new IndexAnnotation(new IndexAttribute())); builder.Entity() - .HasMany(application => application.Authorizations) - .WithOptional(authorization => authorization.Application) - .Map(association => association.MapKey("ApplicationId")); + .Property(application => application.Type) + .IsRequired(); builder.Entity() - .HasMany(application => application.Tokens) - .WithOptional(token => token.Application) - .Map(association => association.MapKey("ApplicationId")); + .HasMany(application => application.Authorizations) + .WithOptional(authorization => authorization.Application) + .Map(association => association.MapKey("ApplicationId")); builder.Entity() - .ToTable("OpenIddictApplications"); + .HasMany(application => application.Tokens) + .WithOptional(token => token.Application) + .Map(association => association.MapKey("ApplicationId")); + + builder.Entity() + .ToTable("OpenIddictApplications"); // Configure the TAuthorization entity. builder.Entity() - .HasKey(authorization => authorization.Id); + .HasKey(authorization => authorization.Id); + + builder.Entity() + .Property(authorization => authorization.Status) + .IsRequired(); + + builder.Entity() + .Property(authorization => authorization.Subject) + .IsRequired(); + + builder.Entity() + .Property(authorization => authorization.Type) + .IsRequired(); builder.Entity() - .HasMany(application => application.Tokens) - .WithOptional(token => token.Authorization) - .Map(association => association.MapKey("AuthorizationId")); + .HasMany(application => application.Tokens) + .WithOptional(token => token.Authorization) + .Map(association => association.MapKey("AuthorizationId")); builder.Entity() - .ToTable("OpenIddictAuthorizations"); + .ToTable("OpenIddictAuthorizations"); // Configure the TScope entity. builder.Entity() - .HasKey(scope => scope.Id); + .HasKey(scope => scope.Id); + + builder.Entity() + .Property(scope => scope.Name) + .IsRequired(); builder.Entity() - .ToTable("OpenIddictScopes"); + .ToTable("OpenIddictScopes"); // Configure the TToken entity. builder.Entity() - .HasKey(token => token.Id); + .HasKey(token => token.Id); + + builder.Entity() + .Property(token => token.Hash) + .HasMaxLength(450) + .HasColumnAnnotation(IndexAnnotation.AnnotationName, new IndexAnnotation(new IndexAttribute())); + + builder.Entity() + .Property(token => token.Subject) + .IsRequired(); builder.Entity() - .Property(token => token.Hash) - .HasMaxLength(450) - .HasColumnAnnotation(IndexAnnotation.AnnotationName, new IndexAnnotation(new IndexAttribute())); + .Property(token => token.Type) + .IsRequired(); builder.Entity() - .ToTable("OpenIddictTokens"); + .ToTable("OpenIddictTokens"); return builder; } diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs index f880d828..8b6aa6b4 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs @@ -146,7 +146,8 @@ namespace OpenIddict.EntityFramework var authorization = new TAuthorization { Status = descriptor.Status, - Subject = descriptor.Subject + Subject = descriptor.Subject, + Type = descriptor.Type }; if (descriptor.Scopes.Count != 0) diff --git a/src/OpenIddict.EntityFrameworkCore/OpenIddictExtensions.cs b/src/OpenIddict.EntityFrameworkCore/OpenIddictExtensions.cs index ca82c718..0e061667 100644 --- a/src/OpenIddict.EntityFrameworkCore/OpenIddictExtensions.cs +++ b/src/OpenIddict.EntityFrameworkCore/OpenIddictExtensions.cs @@ -225,6 +225,12 @@ namespace Microsoft.Extensions.DependencyInjection entity.HasIndex(application => application.ClientId) .IsUnique(unique: true); + entity.Property(application => application.ClientId) + .IsRequired(required: true); + + entity.Property(application => application.Type) + .IsRequired(); + entity.HasMany(application => application.Authorizations) .WithOne(authorization => authorization.Application) .HasForeignKey("ApplicationId") @@ -243,6 +249,15 @@ namespace Microsoft.Extensions.DependencyInjection { entity.HasKey(authorization => authorization.Id); + entity.Property(authorization => authorization.Status) + .IsRequired(); + + entity.Property(authorization => authorization.Subject) + .IsRequired(); + + entity.Property(authorization => authorization.Type) + .IsRequired(); + entity.HasMany(application => application.Tokens) .WithOne(token => token.Authorization) .HasForeignKey("AuthorizationId") @@ -256,6 +271,9 @@ namespace Microsoft.Extensions.DependencyInjection { entity.HasKey(scope => scope.Id); + entity.Property(scope => scope.Name) + .IsRequired(); + entity.ToTable("OpenIddictScopes"); }); @@ -267,6 +285,12 @@ namespace Microsoft.Extensions.DependencyInjection entity.HasIndex(token => token.Hash) .IsUnique(unique: true); + entity.Property(token => token.Subject) + .IsRequired(); + + entity.Property(token => token.Type) + .IsRequired(); + entity.ToTable("OpenIddictTokens"); }); diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs index df97afd9..7089f663 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs @@ -145,7 +145,8 @@ namespace OpenIddict.EntityFrameworkCore var authorization = new TAuthorization { Status = descriptor.Status, - Subject = descriptor.Subject + Subject = descriptor.Subject, + Type = descriptor.Type }; if (descriptor.Scopes.Count != 0) diff --git a/src/OpenIddict.Models/OpenIddictAuthorization.cs b/src/OpenIddict.Models/OpenIddictAuthorization.cs index 5ec20cd3..62b5ac82 100644 --- a/src/OpenIddict.Models/OpenIddictAuthorization.cs +++ b/src/OpenIddict.Models/OpenIddictAuthorization.cs @@ -65,5 +65,10 @@ namespace OpenIddict.Models /// associated with the current authorization. /// public virtual IList Tokens { get; } = new List(); + + /// + /// Gets or sets the type of the current authorization. + /// + public virtual string Type { get; set; } } } diff --git a/src/OpenIddict/OpenIddictProvider.Serialization.cs b/src/OpenIddict/OpenIddictProvider.Serialization.cs index da335e25..f2bb4dee 100644 --- a/src/OpenIddict/OpenIddictProvider.Serialization.cs +++ b/src/OpenIddict/OpenIddictProvider.Serialization.cs @@ -251,7 +251,7 @@ namespace OpenIddict descriptor.AuthorizationId = ticket.GetProperty(OpenIddictConstants.Properties.AuthorizationId); } - // Otherwise, create an ad-hoc authorization if the token is an authorization code. + // Otherwise, create an ad hoc authorization if the token is an authorization code. else if (type == OpenIdConnectConstants.TokenUsages.AuthorizationCode) { Debug.Assert(!string.IsNullOrEmpty(descriptor.ApplicationId), "The client identifier shouldn't be null."); @@ -261,7 +261,7 @@ namespace OpenIddict { descriptor.AuthorizationId = await Authorizations.GetIdAsync(authorization, context.RequestAborted); - Logger.LogInformation("An ad-hoc authorization was automatically created and " + + Logger.LogInformation("An ad hoc authorization was automatically created and " + "associated with the '{ClientId}' application: {Identifier}.", request.ClientId, descriptor.AuthorizationId); } @@ -401,7 +401,8 @@ namespace OpenIddict { ApplicationId = token.ApplicationId, Status = OpenIddictConstants.Statuses.Valid, - Subject = token.Subject + Subject = token.Subject, + Type = OpenIddictConstants.AuthorizationTypes.AdHoc }; foreach (var scope in request.GetScopes()) diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs index 0179b014..5282db38 100644 --- a/test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs +++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs @@ -571,7 +571,7 @@ namespace OpenIddict.Tests builder.Services.AddSingleton(CreateTokenManager(instance => { instance.Setup(mock => mock.CreateAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(token); + .ReturnsAsync(token); instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); @@ -596,7 +596,8 @@ namespace OpenIddict.Tests Mock.Get(manager).Verify(mock => mock.CreateAsync( It.Is(descriptor => descriptor.ApplicationId == "3E228451-1555-46F7-A471-951EFBA23A56" && - descriptor.Subject == "Bob le Magnifique"), + descriptor.Subject == "Bob le Magnifique" && + descriptor.Type == OpenIddictConstants.AuthorizationTypes.AdHoc), It.IsAny()), Times.Once()); }