diff --git a/samples/Mvc.Server/Startup.cs b/samples/Mvc.Server/Startup.cs index e99a1b5c..9459f231 100644 --- a/samples/Mvc.Server/Startup.cs +++ b/samples/Mvc.Server/Startup.cs @@ -142,7 +142,7 @@ namespace Mvc.Server { // context.Applications.Add(new OpenIddictApplication { // Id = "resource_server", // DisplayName = "Main resource server", - // Secret = "875sqd4s5d748z78z7ds1ff8zz8814ff88ed8ea4z4zzd", + // Secret = Crypto.HashPassword("secret_secret_secret"), // Type = OpenIddictConstants.ClientTypes.Confidential // }); diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Revocation.cs b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Revocation.cs index 212dac84..d5801fec 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Revocation.cs +++ b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Revocation.cs @@ -5,11 +5,13 @@ */ using System; +using System.Diagnostics; using System.Threading.Tasks; using AspNet.Security.OpenIdConnect.Extensions; using AspNet.Security.OpenIdConnect.Server; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace OpenIddict.Infrastructure { public partial class OpenIddictProvider : OpenIdConnectServerProvider @@ -88,24 +90,24 @@ namespace OpenIddict.Infrastructure { // If the received token is not a refresh token, set Revoked // to false to indicate that the token cannot be revoked. if (!context.Ticket.IsRefreshToken()) { + services.Logger.LogError("The revocation request was rejected because the token was not a refresh token."); + context.Revoked = false; return; } // Extract the token identifier from the authentication ticket. - // If the identifier cannot be extracted, abort the revocation. var identifier = context.Ticket.GetTicketId(); - if (string.IsNullOrEmpty(identifier)) { - context.Revoked = true; - - return; - } + Debug.Assert(!string.IsNullOrEmpty(identifier), + "The refresh token should contain a ticket identifier."); // Retrieve the token from the database. If the token cannot be found, // assume it is invalid and consider the revocation as successful. var token = await services.Tokens.FindByIdAsync(identifier); if (token == null) { + services.Logger.LogInformation("The refresh token '{Identifier}' was already revoked.", identifier); + context.Revoked = true; return; @@ -114,6 +116,8 @@ namespace OpenIddict.Infrastructure { // Revoke the refresh token. await services.Tokens.RevokeAsync(token); + services.Logger.LogInformation("The refresh token '{Identifier}' was revoked.", identifier); + context.Revoked = true; } } diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs index d462e9a8..c81909f9 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs +++ b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs @@ -4,7 +4,7 @@ * the license and the contributors participating to this project. */ -using System.Diagnostics; +using System; using System.Threading.Tasks; using AspNet.Security.OpenIdConnect.Extensions; using AspNet.Security.OpenIdConnect.Server; @@ -17,14 +17,16 @@ namespace OpenIddict.Infrastructure { public override async Task SerializeRefreshToken([NotNull] SerializeRefreshTokenContext context) { var services = context.HttpContext.RequestServices.GetRequiredService>(); - // Note: the identifier cannot be null as it's always - // generated by the OpenID Connect server middleware - // before invoking the SerializeRefreshToken event. - Debug.Assert(!string.IsNullOrEmpty(context.Ticket.GetTicketId()), - "The unique identifier associated with the refresh token was missing or empty."); + // Persist a new token entry in the database. + var identifier = await services.Tokens.CreateAsync(OpenIdConnectConstants.TokenTypeHints.RefreshToken); + if (string.IsNullOrEmpty(identifier)) { + throw new InvalidOperationException("The unique key associated with a refresh token cannot be null or empty."); + } - // Only persist the refresh token identifier in the database. - await services.Tokens.CreateAsync(context.Ticket.GetTicketId(), OpenIdConnectConstants.TokenTypeHints.RefreshToken); + // Attach the key returned by the underlying store + // to the refresh token to override the default GUID + // generated by the OpenID Connect server middleware. + context.Ticket.SetTicketId(identifier); } } } \ No newline at end of file diff --git a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs index 7f309c1f..9056479f 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs @@ -52,8 +52,11 @@ namespace OpenIddict { /// Creates a new application. /// /// The application to create. - /// A that can be used to monitor the asynchronous operation. - public virtual Task CreateAsync(TApplication application) { + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the unique identifier associated with the application. + /// + public virtual Task CreateAsync(TApplication application) { if (application == null) { throw new ArgumentNullException(nameof(application)); } @@ -65,7 +68,10 @@ namespace OpenIddict { /// Retrieves an application using its unique identifier. /// /// The unique identifier associated with the application. - /// A that can be used to monitor the asynchronous operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the client application corresponding to the identifier. + /// public virtual Task FindByIdAsync(string identifier) { return Store.FindByIdAsync(identifier, CancellationToken); } @@ -74,7 +80,10 @@ namespace OpenIddict { /// Retrieves an application using its post_logout_redirect_uri. /// /// The post_logout_redirect_uri associated with the application. - /// A that can be used to monitor the asynchronous operation. + /// + /// A that can be used to monitor the asynchronous operation, whose result + /// returns the client application corresponding to the post_logout_redirect_uri. + /// public virtual Task FindByLogoutRedirectUri(string url) { return Store.FindByLogoutRedirectUri(url, CancellationToken); } @@ -83,7 +92,10 @@ namespace OpenIddict { /// Retrieves the client type associated with an application. /// /// The application. - /// A that can be used to monitor the asynchronous operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the client type of the application (by default, "public"). + /// public virtual async Task GetClientTypeAsync(TApplication application) { if (application == null) { throw new ArgumentNullException(nameof(application)); @@ -105,7 +117,10 @@ namespace OpenIddict { /// Retrieves the display name associated with an application. /// /// The application. - /// A that can be used to monitor the asynchronous operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the display name associated with the application. + /// public virtual Task GetDisplayNameAsync(TApplication application) { if (application == null) { throw new ArgumentNullException(nameof(application)); @@ -119,7 +134,10 @@ namespace OpenIddict { /// /// The application. /// The address that should be compared to the redirect_uri stored in the database. - /// A that can be used to monitor the asynchronous operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns a boolean indicating whether the redirect_uri was valid. + /// public virtual async Task ValidateRedirectUriAsync(TApplication application, string address) { if (application == null) { throw new ArgumentNullException(nameof(application)); @@ -141,6 +159,10 @@ namespace OpenIddict { /// The application. /// The secret that should be compared to the client_secret stored in the database. /// A that can be used to monitor the asynchronous operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns a boolean indicating whether the client secret was valid. + /// public virtual async Task ValidateSecretAsync(TApplication application, string secret) { if (application == null) { throw new ArgumentNullException(nameof(application)); diff --git a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs index 943a7d05..a4cc135a 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs @@ -75,7 +75,10 @@ namespace OpenIddict { /// /// The user corresponding to the identity. /// The scopes granted by the resource owner. - /// A that can be used to monitor the asynchronous operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the corresponding to the user. + /// public virtual async Task CreateIdentityAsync(TUser user, IEnumerable scopes) { if (user == null) { throw new ArgumentNullException(nameof(user)); @@ -145,26 +148,27 @@ namespace OpenIddict { /// /// Creates a new token, defined by a unique identifier and a token type. /// - /// The unique identifier associated with the token to create. /// The token type. - /// A that can be used to monitor the asynchronous operation. - public virtual Task CreateAsync(string identifier, string type) { - if (string.IsNullOrEmpty(identifier)) { - throw new ArgumentException("The identifier cannot be null or empty", nameof(identifier)); - } - + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the unique identifier associated with the token. + /// + public virtual Task CreateAsync(string type) { if (string.IsNullOrEmpty(type)) { throw new ArgumentException("The token type cannot be null or empty", nameof(type)); } - return Store.CreateAsync(identifier, type, CancellationToken); + return Store.CreateAsync(type, CancellationToken); } /// - /// Retrieves an token using its unique identifier. + /// Retrieves a token using its unique identifier. /// /// The unique identifier associated with the token. - /// A that can be used to monitor the asynchronous operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the token corresponding to the unique identifier. + /// public virtual Task FindByIdAsync(string identifier) { if (string.IsNullOrEmpty(identifier)) { throw new ArgumentException("The identifier cannot be null or empty", nameof(identifier)); diff --git a/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs index 0bd0c9ed..b900e7f0 100644 --- a/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs +++ b/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs @@ -18,15 +18,21 @@ namespace OpenIddict { /// /// The application to create. /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - Task CreateAsync(TApplication application, CancellationToken cancellationToken); + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the unique identifier associated with the application. + /// + Task CreateAsync(TApplication application, CancellationToken cancellationToken); /// /// Retrieves an application using its unique identifier. /// /// The unique identifier associated with the application. /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the client application corresponding to the identifier. + /// Task FindByIdAsync(string identifier, CancellationToken cancellationToken); /// @@ -34,7 +40,10 @@ namespace OpenIddict { /// /// The post_logout_redirect_uri associated with the application. /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. + /// + /// A that can be used to monitor the asynchronous operation, whose result + /// returns the client application corresponding to the post_logout_redirect_uri. + /// Task FindByLogoutRedirectUri(string url, CancellationToken cancellationToken); /// @@ -42,7 +51,10 @@ namespace OpenIddict { /// /// The application. /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the client type of the application (by default, "public"). + /// Task GetClientTypeAsync(TApplication application, CancellationToken cancellationToken); /// @@ -50,7 +62,10 @@ namespace OpenIddict { /// /// The application. /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the display name associated with the application. + /// Task GetDisplayNameAsync(TApplication application, CancellationToken cancellationToken); /// @@ -58,7 +73,10 @@ namespace OpenIddict { /// /// The application. /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the redirect_uri associated with the application. + /// Task GetRedirectUriAsync(TApplication application, CancellationToken cancellationToken); /// @@ -66,7 +84,10 @@ namespace OpenIddict { /// /// The application. /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the hashed secret associated with the application. + /// Task GetHashedSecretAsync(TApplication application, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs index 7ce31f81..6450b91f 100644 --- a/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs +++ b/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs @@ -16,18 +16,23 @@ namespace OpenIddict { /// /// Creates a new token, defined by a unique identifier and a token type. /// - /// The unique identifier associated with the token to create. /// The token type. /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - Task CreateAsync(string identifier, string type, CancellationToken cancellationToken); + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the unique identifier associated with the token. + /// + Task CreateAsync(string type, CancellationToken cancellationToken); /// /// Retrieves an token using its unique identifier. /// /// The unique identifier associated with the token. /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the token corresponding to the unique identifier. + /// Task FindByIdAsync(string identifier, CancellationToken cancellationToken); /// diff --git a/src/OpenIddict.EntityFramework/Models/OpenIddictApplication.cs b/src/OpenIddict.EntityFramework/Models/OpenIddictApplication.cs index f9c20262..f2e798c7 100644 --- a/src/OpenIddict.EntityFramework/Models/OpenIddictApplication.cs +++ b/src/OpenIddict.EntityFramework/Models/OpenIddictApplication.cs @@ -55,6 +55,6 @@ namespace OpenIddict { /// Gets or sets the application type /// associated with the current application. /// - public virtual string Type { get; set; } + public virtual string Type { get; set; } = OpenIddictConstants.ClientTypes.Public; } } \ No newline at end of file diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs index 93f9a679..b0e0b80b 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs @@ -41,14 +41,22 @@ namespace OpenIddict { /// The application to create. /// The that can be used to abort the operation. /// A that can be used to monitor the asynchronous operation. - public virtual Task CreateAsync(TApplication application, CancellationToken cancellationToken) { + public virtual async Task CreateAsync(TApplication application, CancellationToken cancellationToken) { if (application == null) { throw new ArgumentNullException(nameof(application)); } + // Ensure that the key type can be serialized. + var converter = TypeDescriptor.GetConverter(typeof(TKey)); + if (!converter.CanConvertTo(typeof(string))) { + throw new InvalidOperationException($"The '{typeof(TKey).Name}' key type is not supported."); + } + Context.Add(application); - return Context.SaveChangesAsync(cancellationToken); + await Context.SaveChangesAsync(cancellationToken); + + return converter.ConvertToInvariantString(application.Id); } /// @@ -56,7 +64,10 @@ namespace OpenIddict { /// /// The unique identifier associated with the application. /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the client application corresponding to the identifier. + /// public virtual Task FindByIdAsync(string identifier, CancellationToken cancellationToken) { var converter = TypeDescriptor.GetConverter(typeof(TKey)); @@ -76,7 +87,10 @@ namespace OpenIddict { /// /// The post_logout_redirect_uri associated with the application. /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. + /// + /// A that can be used to monitor the asynchronous operation, whose result + /// returns the client application corresponding to the post_logout_redirect_uri. + /// public virtual Task FindByLogoutRedirectUri(string url, CancellationToken cancellationToken) { return Applications.SingleOrDefaultAsync(application => application.LogoutRedirectUri == url, cancellationToken); } @@ -86,7 +100,10 @@ namespace OpenIddict { /// /// The application. /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the client type of the application (by default, "public"). + /// public virtual Task GetClientTypeAsync(TApplication application, CancellationToken cancellationToken) { if (application == null) { throw new ArgumentNullException(nameof(application)); @@ -100,7 +117,10 @@ namespace OpenIddict { /// /// The application. /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the display name associated with the application. + /// public virtual Task GetDisplayNameAsync(TApplication application, CancellationToken cancellationToken) { if (application == null) { throw new ArgumentNullException(nameof(application)); @@ -114,7 +134,10 @@ namespace OpenIddict { /// /// The application. /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the redirect_uri associated with the application. + /// public virtual Task GetRedirectUriAsync(TApplication application, CancellationToken cancellationToken) { if (application == null) { throw new ArgumentNullException(nameof(application)); @@ -128,7 +151,10 @@ namespace OpenIddict { /// /// The application. /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the hashed secret associated with the application. + /// public virtual Task GetHashedSecretAsync(TApplication application, CancellationToken cancellationToken) { if (application == null) { throw new ArgumentNullException(nameof(application)); diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs index 3986f8cb..26f62789 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs @@ -38,24 +38,25 @@ namespace OpenIddict { /// /// Creates a new token, defined by a unique identifier and a token type. /// - /// The unique identifier associated with the token to create. /// The token type. /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - public virtual Task CreateAsync(string identifier, string type, CancellationToken cancellationToken) { + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the unique identifier associated with the token. + /// + public virtual async Task CreateAsync(string type, CancellationToken cancellationToken) { + // Ensure that the key type can be serialized. var converter = TypeDescriptor.GetConverter(typeof(TKey)); - - // Ensure that the key type is compatible with string keys. - if (!converter.CanConvertFrom(typeof(string))) { - return Task.FromResult(0); + if (!converter.CanConvertTo(typeof(string))) { + throw new InvalidOperationException($"The '{typeof(TKey).Name}' key type is not supported."); } - var key = (TKey) converter.ConvertFromInvariantString(identifier); - - var token = new TToken { Id = key, Type = type }; + var token = new TToken { Type = type }; Tokens.Add(token); - return Context.SaveChangesAsync(cancellationToken); + await Context.SaveChangesAsync(cancellationToken); + + return converter.ConvertToInvariantString(token.Id); } /// @@ -63,12 +64,14 @@ namespace OpenIddict { /// /// The unique identifier associated with the token. /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the token corresponding to the unique identifier. + /// public virtual Task FindByIdAsync(string identifier, CancellationToken cancellationToken) { - var converter = TypeDescriptor.GetConverter(typeof(TKey)); - // If the string key cannot be converted to TKey, return null // to indicate that the requested token doesn't exist. + var converter = TypeDescriptor.GetConverter(typeof(TKey)); if (!converter.CanConvertFrom(typeof(string))) { return Task.FromResult(null); }