diff --git a/samples/Mvc.Server/Controllers/AuthorizationController.cs b/samples/Mvc.Server/Controllers/AuthorizationController.cs index 3290abce..2ba9710a 100644 --- a/samples/Mvc.Server/Controllers/AuthorizationController.cs +++ b/samples/Mvc.Server/Controllers/AuthorizationController.cs @@ -5,6 +5,7 @@ */ using System; +using System.Linq; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; @@ -52,7 +53,9 @@ namespace Mvc.Server { return View(new AuthorizeViewModel { ApplicationName = application.DisplayName, - Parameters = request.Parameters, + Parameters = request.ToDictionary( + parameter => parameter.Key, + parameter => (string) parameter.Value), Scope = request.Scope }); } @@ -101,7 +104,9 @@ namespace Mvc.Server { var request = HttpContext.GetOpenIdConnectRequest(); return View(new LogoutViewModel { - Parameters = request.Parameters + Parameters = request.ToDictionary( + parameter => parameter.Key, + parameter => (string) parameter.Value) }); } diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Authentication.cs b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Authentication.cs index a894aefc..f01a6735 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Authentication.cs +++ b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Authentication.cs @@ -6,6 +6,7 @@ using System; using System.Diagnostics; +using System.IO; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; @@ -20,6 +21,8 @@ using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Bson; namespace OpenIddict.Infrastructure { public partial class OpenIddictProvider : OpenIdConnectServerProvider @@ -28,7 +31,7 @@ namespace OpenIddict.Infrastructure { var services = context.HttpContext.RequestServices.GetRequiredService>(); // Reject requests using the unsupported request parameter. - if (!string.IsNullOrEmpty(context.Request.GetParameter(OpenIdConnectConstants.Parameters.Request))) { + if (!string.IsNullOrEmpty(context.Request.Request)) { services.Logger.LogError("The authorization request was rejected because it contained " + "an unsupported parameter: {Parameter}.", "request"); @@ -53,8 +56,8 @@ namespace OpenIddict.Infrastructure { // If a request_id parameter can be found in the authorization request, // restore the complete authorization request stored in the distributed cache. - if (!string.IsNullOrEmpty(context.Request.GetRequestId())) { - var payload = await services.Options.Cache.GetAsync(OpenIddictConstants.Environment.Request + context.Request.GetRequestId()); + if (!string.IsNullOrEmpty(context.Request.RequestId)) { + var payload = await services.Options.Cache.GetAsync(OpenIddictConstants.Environment.Request + context.Request.RequestId); if (payload == null) { services.Logger.LogError("The authorization request was rejected because an unknown " + "or invalid request_id parameter was specified."); @@ -67,7 +70,14 @@ namespace OpenIddict.Infrastructure { } // Restore the authorization request parameters from the serialized payload. - context.Request.Import(payload); + using (var reader = new BsonReader(new MemoryStream(payload))) { + var serializer = JsonSerializer.CreateDefault(); + + // Note: JsonSerializer.Populate() automatically preserves + // the original request parameters resolved from the cache + // when parameters with the same names are specified. + serializer.Populate(reader, context.Request); + } } } @@ -344,18 +354,26 @@ namespace OpenIddict.Infrastructure { // If no request_id parameter can be found in the current request, assume the OpenID Connect request // was not serialized yet and store the entire payload in the distributed cache to make it easier // to flow across requests and internal/external authentication/registration workflows. - if (string.IsNullOrEmpty(context.Request.GetRequestId())) { + if (string.IsNullOrEmpty(context.Request.RequestId)) { // Generate a request identifier. Note: using a crypto-secure // random number generator is not necessary in this case. var identifier = Guid.NewGuid().ToString(); + // Store the serialized authorization request parameters in the distributed cache. + var stream = new MemoryStream(); + using (var writer = new BsonWriter(stream)) { + writer.CloseOutput = false; + + var serializer = JsonSerializer.CreateDefault(); + serializer.Serialize(writer, context.Request); + } + var options = new DistributedCacheEntryOptions { AbsoluteExpiration = context.Options.SystemClock.UtcNow + TimeSpan.FromMinutes(30), SlidingExpiration = TimeSpan.FromMinutes(10) }; - // Store the serialized authorization request parameters in the distributed cache. - await services.Options.Cache.SetAsync(OpenIddictConstants.Environment.Request + identifier, context.Request.Export(), options); + await services.Options.Cache.SetAsync(OpenIddictConstants.Environment.Request + identifier, stream.ToArray(), options); // Create a new authorization request containing only the request_id parameter. var address = QueryHelpers.AddQueryString( @@ -378,27 +396,23 @@ namespace OpenIddict.Infrastructure { var services = context.HttpContext.RequestServices.GetRequiredService>(); // Remove the authorization request from the distributed cache. - if (!string.IsNullOrEmpty(context.Request.GetRequestId())) { + if (!string.IsNullOrEmpty(context.Request.RequestId)) { // Note: the ApplyAuthorizationResponse event is called for both successful // and errored authorization responses but discrimination is not necessary here, // as the authorization request must be removed from the distributed cache in both cases. - await services.Options.Cache.RemoveAsync(OpenIddictConstants.Environment.Request + context.Request.GetRequestId()); + await services.Options.Cache.RemoveAsync(OpenIddictConstants.Environment.Request + context.Request.RequestId); } if (!context.Options.ApplicationCanDisplayErrors && !string.IsNullOrEmpty(context.Response.Error) && string.IsNullOrEmpty(context.Response.RedirectUri)) { // Determine if the status code pages middleware has been enabled for this request. - // If it was not registered or disabled, let the OpenID Connect server middleware render + // If it was not registered or enabled, let the OpenID Connect server middleware render // a default error page instead of delegating the rendering to the status code middleware. var feature = context.HttpContext.Features.Get(); if (feature != null && feature.Enabled) { // Replace the default status code to return a 400 response. context.HttpContext.Response.StatusCode = 400; - // Store the OpenID Connect response in the HTTP context to allow retrieving it - // from user code (e.g from an ASP.NET Core MVC controller or a Nancy module). - context.HttpContext.SetOpenIdConnectResponse(context.Response); - // Mark the request as fully handled to prevent the OpenID Connect server middleware // from displaying the default error page and to allow the status code pages middleware // to rewrite the response using the logic defined by the developer when registering it. diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Revocation.cs b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Revocation.cs index e193607b..87d0a2ec 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Revocation.cs +++ b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Revocation.cs @@ -20,9 +20,9 @@ namespace OpenIddict.Infrastructure { var services = context.HttpContext.RequestServices.GetRequiredService>(); // When token_type_hint is specified, reject the request if it doesn't correspond to a revocable token. - if (!string.IsNullOrEmpty(context.Request.GetTokenTypeHint()) && - !string.Equals(context.Request.GetTokenTypeHint(), OpenIdConnectConstants.TokenTypeHints.AuthorizationCode) && - !string.Equals(context.Request.GetTokenTypeHint(), OpenIdConnectConstants.TokenTypeHints.RefreshToken)) { + if (!string.IsNullOrEmpty(context.Request.TokenTypeHint) && + !string.Equals(context.Request.TokenTypeHint, OpenIdConnectConstants.TokenTypeHints.AuthorizationCode) && + !string.Equals(context.Request.TokenTypeHint, OpenIdConnectConstants.TokenTypeHints.RefreshToken)) { context.Reject( error: OpenIdConnectConstants.Errors.UnsupportedTokenType, description: "Only authorization codes and refresh tokens can be revoked. When specifying a token_type_hint " + diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs index 5cc51a02..d8148c6c 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs +++ b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs @@ -12,7 +12,6 @@ using AspNet.Security.OpenIdConnect.Extensions; using AspNet.Security.OpenIdConnect.Server; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; -using Microsoft.IdentityModel.Protocols.OpenIdConnect; namespace OpenIddict.Infrastructure { public partial class OpenIddictProvider : OpenIdConnectServerProvider @@ -20,7 +19,7 @@ namespace OpenIddict.Infrastructure { public override async Task SerializeAuthorizationCode([NotNull] SerializeAuthorizationCodeContext context) { var services = context.HttpContext.RequestServices.GetRequiredService>(); - Debug.Assert(context.Request.RequestType == OpenIdConnectRequestType.Authentication, "The request should be an authorization request."); + Debug.Assert(context.Request.IsAuthorizationRequest(), "The request should be an authorization request."); Debug.Assert(!string.IsNullOrEmpty(context.Request.ClientId), "The client_id parameter shouldn't be null or empty."); // Note: a null value could be returned by FindByIdAsync. In this case, throw an exception to abort the token request. @@ -35,8 +34,11 @@ namespace OpenIddict.Infrastructure { throw new InvalidOperationException("The application cannot be retrieved from the database."); } - // Persist a new token entry in the database and attach it to the user and the client application it is issued to. - var identifier = await services.Users.CreateTokenAsync(user, context.Request.ClientId, OpenIdConnectConstants.TokenTypeHints.AuthorizationCode); + // Persist a new token entry in the database and attach it + // to the user and the client application it is issued to. + var identifier = await services.Users.CreateTokenAsync(user, context.Request.ClientId, + OpenIdConnectConstants.TokenTypeHints.AuthorizationCode); + if (string.IsNullOrEmpty(identifier)) { throw new InvalidOperationException("The unique key associated with an authorization code cannot be null or empty."); } @@ -50,8 +52,9 @@ namespace OpenIddict.Infrastructure { public override async Task SerializeRefreshToken([NotNull] SerializeRefreshTokenContext context) { var services = context.HttpContext.RequestServices.GetRequiredService>(); - Debug.Assert(context.Request.RequestType == OpenIdConnectRequestType.Token, "The request should be a token request."); - Debug.Assert(!context.Request.IsClientCredentialsGrantType(), "A refresh token should not be issued when using grant_type=client_credentials."); + Debug.Assert(context.Request.IsTokenRequest(), "The request should be a token request."); + Debug.Assert(!context.Request.IsClientCredentialsGrantType(), + "A refresh token should not be issued when using grant_type=client_credentials."); // Note: a null value could be returned by FindByIdAsync if the user was removed after the initial // check made by HandleTokenRequest. In this case, throw an exception to abort the token request. diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Session.cs b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Session.cs index 2d10d04d..e2efa1e1 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Session.cs +++ b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Session.cs @@ -112,17 +112,13 @@ namespace OpenIddict.Infrastructure { if (!context.Options.ApplicationCanDisplayErrors && !string.IsNullOrEmpty(context.Response.Error) && string.IsNullOrEmpty(context.Response.PostLogoutRedirectUri)) { // Determine if the status code pages middleware has been enabled for this request. - // If it was not registered or disabled, let the OpenID Connect server middleware render + // If it was not registered or enabled, let the OpenID Connect server middleware render // a default error page instead of delegating the rendering to the status code middleware. var feature = context.HttpContext.Features.Get(); if (feature != null && feature.Enabled) { - // Replace the default status code to return a 400 response. + // Replace the default status code by a 400 response. context.HttpContext.Response.StatusCode = 400; - // Store the OpenID Connect response in the HTTP context to allow retrieving it - // from user code (e.g from an ASP.NET Core MVC controller or a Nancy module). - context.HttpContext.SetOpenIdConnectResponse(context.Response); - // Mark the request as fully handled to prevent the OpenID Connect server middleware // from displaying the default error page and to allow the status code pages middleware // to rewrite the response using the logic defined by the developer when registering it. diff --git a/src/OpenIddict.Core/OpenIddictBuilder.cs b/src/OpenIddict.Core/OpenIddictBuilder.cs index 19324381..69d5de68 100644 --- a/src/OpenIddict.Core/OpenIddictBuilder.cs +++ b/src/OpenIddict.Core/OpenIddictBuilder.cs @@ -10,13 +10,11 @@ using System.IdentityModel.Tokens.Jwt; using System.IO; using System.Linq; using System.Reflection; -using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using AspNet.Security.OpenIdConnect.Extensions; using JetBrains.Annotations; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; -using Microsoft.IdentityModel.Tokens; using OpenIddict; namespace Microsoft.AspNetCore.Builder { @@ -286,49 +284,7 @@ namespace Microsoft.AspNetCore.Builder { /// /// The . public virtual OpenIddictBuilder AddEphemeralSigningKey() { - // Note: a 1024-bit key might be returned by RSA.Create() on .NET Desktop/Mono, - // where RSACryptoServiceProvider is still the default implementation and - // where custom implementations can be registered via CryptoConfig. - // To ensure the key size is always acceptable, replace it if necessary. - var algorithm = RSA.Create(); - - if (algorithm.KeySize < 2048) { - algorithm.KeySize = 2048; - } - -#if NET451 - // Note: RSACng cannot be used as it's not available on Mono. - if (algorithm.KeySize < 2048 && algorithm is RSACryptoServiceProvider) { - algorithm.Dispose(); - algorithm = new RSACryptoServiceProvider(2048); - } -#endif - - if (algorithm.KeySize < 2048) { - throw new InvalidOperationException("The ephemeral key generation failed."); - } - - // Note: the RSA instance cannot be flowed as-is due to a bug in IdentityModel that disposes - // the underlying algorithm when it can be cast to RSACryptoServiceProvider. To work around - // this bug, the RSA public/private parameters are manually exported and re-imported when needed. - SecurityKey key; -#if NET451 - if (algorithm is RSACryptoServiceProvider) { - var parameters = algorithm.ExportParameters(includePrivateParameters: true); - key = new RsaSecurityKey(parameters); - - // Dispose the algorithm instance. - algorithm.Dispose(); - } - - else { -#endif - key = new RsaSecurityKey(algorithm); -#if NET451 - } -#endif - - return Configure(options => options.SigningCredentials.AddKey(key)); + return Configure(options => options.SigningCredentials.AddEphemeralKey()); } /// diff --git a/src/OpenIddict.Core/OpenIddictOptions.cs b/src/OpenIddict.Core/OpenIddictOptions.cs index d8d377c4..5f4d2cf4 100644 --- a/src/OpenIddict.Core/OpenIddictOptions.cs +++ b/src/OpenIddict.Core/OpenIddictOptions.cs @@ -16,9 +16,6 @@ namespace OpenIddict { /// public class OpenIddictOptions : OpenIdConnectServerOptions { public OpenIddictOptions() { - AuthorizationEndpointPath = IntrospectionEndpointPath = LogoutEndpointPath = - RevocationEndpointPath = TokenEndpointPath = UserinfoEndpointPath = PathString.Empty; - // Use the same lifespan as the default security stamp // verification interval used by ASP.NET Core Identity. AccessTokenLifetime = TimeSpan.FromMinutes(30); diff --git a/src/OpenIddict.Core/project.json b/src/OpenIddict.Core/project.json index f1d8c6f5..16468f52 100644 --- a/src/OpenIddict.Core/project.json +++ b/src/OpenIddict.Core/project.json @@ -33,7 +33,7 @@ }, "dependencies": { - "AspNet.Security.OpenIdConnect.Server": "1.0.0-beta6-final", + "AspNet.Security.OpenIdConnect.Server": "1.0.0-beta7-*", "CryptoHelper": "2.0.0", "JetBrains.Annotations": { "type": "build", "version": "10.1.4" }, "Microsoft.AspNetCore.Diagnostics.Abstractions": "1.0.0",