diff --git a/samples/Mvc.Server/Startup.cs b/samples/Mvc.Server/Startup.cs index 11d8d895..0daa3a5f 100644 --- a/samples/Mvc.Server/Startup.cs +++ b/samples/Mvc.Server/Startup.cs @@ -76,9 +76,7 @@ namespace Mvc.Server { // Note: OpenIddict must be added after // ASP.NET Identity and the external providers. - app.UseOpenIddict(options => { - options.AllowInsecureHttp = true; - }); + app.UseOpenIddict(); app.UseMvcWithDefaultRoute(); diff --git a/src/OpenIddict.Core/OpenIddictBuilder.cs b/src/OpenIddict.Core/OpenIddictBuilder.cs index 0db22be7..9213149a 100644 --- a/src/OpenIddict.Core/OpenIddictBuilder.cs +++ b/src/OpenIddict.Core/OpenIddictBuilder.cs @@ -1,36 +1,21 @@ -/* - * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) - * See https://github.com/openiddict/core for more information concerning - * the license and the contributors participating to this project. - */ +using OpenIddict; -using System; -using Microsoft.Extensions.DependencyInjection; - -namespace OpenIddict { - public class OpenIddictBuilder { - internal OpenIddictBuilder(IServiceCollection services) { - Services = services; +namespace Microsoft.AspNet.Builder { + /// + /// Holds various properties allowing to configure OpenIddct. + /// + public class OpenIddictBuilder : OpenIdConnectServerBuilder { + public OpenIddictBuilder(IApplicationBuilder builder) + : base(builder) { + Options = new OpenIddictOptions(); } /// - /// Gets or sets the type corresponding to the Application entity. - /// - public Type ApplicationType { get; set; } - - /// - /// Gets or sets the type corresponding to the Role entity. + /// Gets or sets the options used by OpenIddict. /// - public Type RoleType { get; set; } - - /// - /// Gets or sets the type corresponding to the User entity. - /// - public Type UserType { get; set; } - - /// - /// Gets the services used by OpenIddict. - /// - public IServiceCollection Services { get; } + public new OpenIddictOptions Options { + get { return base.Options as OpenIddictOptions; } + set { base.Options = value; } + } } -} \ No newline at end of file +} diff --git a/src/OpenIddict.Core/OpenIddictController.cs b/src/OpenIddict.Core/OpenIddictController.cs index a5131a30..7c4ad440 100644 --- a/src/OpenIddict.Core/OpenIddictController.cs +++ b/src/OpenIddict.Core/OpenIddictController.cs @@ -10,8 +10,8 @@ using System.Security.Claims; using System.Threading; using System.Threading.Tasks; using AspNet.Security.OpenIdConnect.Extensions; -using AspNet.Security.OpenIdConnect.Server; using Microsoft.AspNet.Authorization; +using Microsoft.AspNet.Builder; using Microsoft.AspNet.Http.Authentication; using Microsoft.AspNet.Mvc; using Microsoft.Extensions.Internal; @@ -20,8 +20,11 @@ using Microsoft.IdentityModel.Protocols.OpenIdConnect; namespace OpenIddict { // Note: this controller is generic and doesn't need to be marked as internal to prevent MVC from discovering it. public class OpenIddictController : Controller where TUser : class where TApplication : class { - public OpenIddictController([NotNull] OpenIddictManager manager) { + public OpenIddictController( + [NotNull] OpenIddictManager manager, + [NotNull] OpenIddictOptions options) { Manager = manager; + Options = options; } /// @@ -32,7 +35,7 @@ namespace OpenIddict { /// /// Gets the OpenIddict options used by the server. /// - protected virtual OpenIddictOptions Options => (OpenIddictOptions) HttpContext.Items[typeof(OpenIddictOptions)]; + protected virtual OpenIddictOptions Options { get; } [HttpGet, HttpPost] public async Task Authorize() { diff --git a/src/OpenIddict.Core/OpenIddictExtensions.cs b/src/OpenIddict.Core/OpenIddictExtensions.cs index 3f825a7b..927b4161 100644 --- a/src/OpenIddict.Core/OpenIddictExtensions.cs +++ b/src/OpenIddict.Core/OpenIddictExtensions.cs @@ -9,6 +9,7 @@ using System.Diagnostics; using System.Reflection; using AspNet.Security.OpenIdConnect.Server; using Microsoft.AspNet.FileProviders; +using Microsoft.AspNet.Hosting; using Microsoft.AspNet.Http; using Microsoft.AspNet.Identity; using Microsoft.AspNet.StaticFiles; @@ -22,8 +23,8 @@ using NWebsec.Owin; namespace Microsoft.AspNet.Builder { public static class OpenIddictExtensions { - public static OpenIddictBuilder AddOpenIddictCore( - [NotNull] this IdentityBuilder builder) where TApplication : class { + public static OpenIddictServices AddOpenIddictCore([NotNull] this IdentityBuilder builder) + where TApplication : class { builder.Services.AddAuthentication(); builder.Services.AddCaching(); @@ -36,7 +37,7 @@ namespace Microsoft.AspNet.Builder { typeof(OpenIddictManager<,>).MakeGenericType( builder.UserType, typeof(TApplication))); - var services = new OpenIddictBuilder(builder.Services) { + var services = new OpenIddictServices(builder.Services) { ApplicationType = typeof(TApplication), RoleType = builder.RoleType, UserType = builder.UserType @@ -47,19 +48,22 @@ namespace Microsoft.AspNet.Builder { return services; } + public static IApplicationBuilder UseOpenIddict([NotNull] this IApplicationBuilder app) { + return app.UseOpenIddict(options => { }); + } + public static IApplicationBuilder UseOpenIddict( [NotNull] this IApplicationBuilder app, - [NotNull] Action configuration) { - var instance = new OpenIddictOptions(); + [NotNull] Action configuration) { + var builder = new OpenIddictBuilder(app); - // Turn ApplicationCanDisplayErrors on to ensure ASP.NET MVC 6 - // handles errored requests and returns an appropriate error page. - instance.ApplicationCanDisplayErrors = true; + // By default, enable AllowInsecureHttp in development/testing environments. + var environment = app.ApplicationServices.GetRequiredService(); + builder.Options.AllowInsecureHttp = environment.IsDevelopment() || environment.IsEnvironment("Testing"); - // Call the configuration delegate defined by the user. - configuration(instance); + configuration(builder); - if (!instance.UseCustomViews) { + if (!builder.Options.UseCustomViews) { app.UseStaticFiles(new StaticFileOptions { FileProvider = new EmbeddedFileProvider( assembly: Assembly.Load(new AssemblyName("OpenIddict.Assets")), @@ -76,24 +80,8 @@ namespace Microsoft.AspNet.Builder { // Add OpenIdConnectServerMiddleware to the ASP.NET 5 pipeline. app.UseOpenIdConnectServer(options => { - // Resolve the OpenIddict provider from the global services container. + options.Options = builder.Options; options.Provider = app.ApplicationServices.GetRequiredService(); - - // Copy the OpenIddict options to the ASOS configuration. - options.Options.AuthenticationScheme = instance.AuthenticationScheme; - - options.Options.Issuer = instance.Issuer; - - options.Options.AuthorizationEndpointPath = instance.AuthorizationEndpointPath; - options.Options.LogoutEndpointPath = instance.LogoutEndpointPath; - - options.Options.AccessTokenLifetime = instance.AccessTokenLifetime; - options.Options.AuthorizationCodeLifetime = instance.AuthorizationCodeLifetime; - options.Options.IdentityTokenLifetime = instance.IdentityTokenLifetime; - options.Options.RefreshTokenLifetime = instance.RefreshTokenLifetime; - - options.Options.ApplicationCanDisplayErrors = instance.ApplicationCanDisplayErrors; - options.Options.AllowInsecureHttp = instance.AllowInsecureHttp; }); #if DNX451 @@ -120,57 +108,45 @@ namespace Microsoft.AspNet.Builder { #endif // Run the rest of the pipeline in an isolated environment. - return app.Isolate(builder => { - // Add the options to the ASP.NET context - // before executing the rest of the pipeline. - builder.Use(next => context => { - context.Items[typeof(OpenIddictOptions)] = instance; + return app.Isolate(container => container.UseMvc(routes => { + // Register the actions corresponding to the authorization endpoint. + if (builder.Options.AuthorizationEndpointPath.HasValue) { + routes.MapRoute("{D97891B4}", builder.Options.AuthorizationEndpointPath.Value.Substring(1), new { + controller = typeof(OpenIddictController<,>).Name, + action = nameof(OpenIddictController.Authorize) + }); - return next(context); - }); + routes.MapRoute("{7148DB83}", builder.Options.AuthorizationEndpointPath.Value.Substring(1) + "/accept", new { + controller = typeof(OpenIddictController<,>).Name, + action = nameof(OpenIddictController.Accept) + }); - // Register ASP.NET MVC 6 and the actions - // associated to the OpenIddict controller. - builder.UseMvc(routes => { - // Register the actions corresponding to the authorization endpoint. - if (instance.AuthorizationEndpointPath.HasValue) { - routes.MapRoute("{D97891B4}", instance.AuthorizationEndpointPath.Value.Substring(1), new { - controller = typeof(OpenIddictController<,>).Name, - action = nameof(OpenIddictController.Authorize) - }); - - routes.MapRoute("{7148DB83}", instance.AuthorizationEndpointPath.Value.Substring(1) + "/accept", new { - controller = typeof(OpenIddictController<,>).Name, - action = nameof(OpenIddictController.Accept) - }); - - routes.MapRoute("{23438BCC}", instance.AuthorizationEndpointPath.Value.Substring(1) + "/deny", new { - controller = typeof(OpenIddictController<,>).Name, - action = nameof(OpenIddictController.Deny) - }); - } - - // Register the action corresponding to the logout endpoint. - if (instance.LogoutEndpointPath.HasValue) { - routes.MapRoute("{C7DB102A}", instance.LogoutEndpointPath.Value.Substring(1), new { - controller = typeof(OpenIddictController<,>).Name, - action = nameof(OpenIddictController.Logout) - }); - } - }); - }, services => { - var builder = app.ApplicationServices.GetRequiredService(); + routes.MapRoute("{23438BCC}", builder.Options.AuthorizationEndpointPath.Value.Substring(1) + "/deny", new { + controller = typeof(OpenIddictController<,>).Name, + action = nameof(OpenIddictController.Deny) + }); + } + + // Register the action corresponding to the logout endpoint. + if (builder.Options.LogoutEndpointPath.HasValue) { + routes.MapRoute("{C7DB102A}", builder.Options.LogoutEndpointPath.Value.Substring(1), new { + controller = typeof(OpenIddictController<,>).Name, + action = nameof(OpenIddictController.Logout) + }); + } + }), services => { + var instance = app.ApplicationServices.GetRequiredService(); services.AddMvc() // Register the OpenIddict controller. .AddControllersAsServices(new[] { - typeof(OpenIddictController<,>).MakeGenericType(builder.UserType, builder.ApplicationType) + typeof(OpenIddictController<,>).MakeGenericType(instance.UserType, instance.ApplicationType) }) // Update the Razor options to use an embedded provider // extracting its views from the current assembly. .AddRazorOptions(options => { - if (!instance.UseCustomViews) { + if (!builder.Options.UseCustomViews) { options.FileProvider = new EmbeddedFileProvider( assembly: typeof(OpenIddictOptions).GetTypeInfo().Assembly, baseNamespace: "OpenIddict.Core"); @@ -178,24 +154,26 @@ namespace Microsoft.AspNet.Builder { }); // Register the sign-in manager in the isolated container. - services.AddScoped(typeof(SignInManager<>).MakeGenericType(builder.UserType), provider => { + services.AddScoped(typeof(SignInManager<>).MakeGenericType(instance.UserType), provider => { var accessor = provider.GetRequiredService(); var container = (IServiceProvider) accessor.HttpContext.Items[typeof(IServiceProvider)]; Debug.Assert(container != null); // Resolve the sign-in manager from the parent container. - return container.GetRequiredService(typeof(SignInManager<>).MakeGenericType(builder.UserType)); + return container.GetRequiredService(typeof(SignInManager<>).MakeGenericType(instance.UserType)); }); // Register the user manager in the isolated container. - services.AddScoped(typeof(OpenIddictManager<,>).MakeGenericType(builder.UserType, builder.ApplicationType), provider => { + services.AddScoped(typeof(OpenIddictManager<,>).MakeGenericType(instance.UserType, instance.ApplicationType), provider => { var accessor = provider.GetRequiredService(); var container = (IServiceProvider) accessor.HttpContext.Items[typeof(IServiceProvider)]; Debug.Assert(container != null); // Resolve the user manager from the parent container. - return container.GetRequiredService(typeof(OpenIddictManager<,>).MakeGenericType(builder.UserType, builder.ApplicationType)); + return container.GetRequiredService(typeof(OpenIddictManager<,>).MakeGenericType(instance.UserType, instance.ApplicationType)); }); + + services.AddScoped(provider => builder.Options); }); } } diff --git a/src/OpenIddict.Core/OpenIddictOptions.cs b/src/OpenIddict.Core/OpenIddictOptions.cs index a43da165..abafec80 100644 --- a/src/OpenIddict.Core/OpenIddictOptions.cs +++ b/src/OpenIddict.Core/OpenIddictOptions.cs @@ -4,81 +4,14 @@ * the license and the contributors participating to this project. */ -using System; using AspNet.Security.OpenIdConnect.Server; -using Microsoft.AspNet.Http; namespace OpenIddict { - public class OpenIddictOptions { - public string AuthenticationScheme { get; set; } = OpenIddictDefaults.AuthenticationScheme; - - /// - /// The base address used to uniquely identify the authorization server. - /// The URI must be absolute and may contain a path, but no query string or fragment part. - /// Unless AllowInsecureHttp has been set to true, an HTTPS address must be provided. - /// - public Uri Issuer { get; set; } - - /// - /// The request path where client applications will redirect the user-agent in order to - /// obtain user consent to issue a token. Must begin with a leading slash, like "/connect/authorize". - /// This setting can be set to to disable the authorization endpoint. - /// - public PathString AuthorizationEndpointPath { get; set; } = new PathString(OpenIdConnectServerDefaults.AuthorizationEndpointPath); - - /// - /// The request path client applications communicate with to log out. - /// Must begin with a leading slash, like "/connect/logout". - /// You can set it to to disable the logout endpoint. - /// - public PathString LogoutEndpointPath { get; set; } = new PathString(OpenIdConnectServerDefaults.LogoutEndpointPath); - - /// - /// The period of time the authorization code remains valid after being issued. The default is 5 minutes. - /// This time span must also take into account clock synchronization between servers in a web farm, so a very - /// brief value could result in unexpectedly expired tokens. - /// - public TimeSpan AuthorizationCodeLifetime { get; set; } = TimeSpan.FromMinutes(5); - - /// - /// The period of time the access token remains valid after being issued. The default is 1 hour. - /// The client application is expected to refresh or acquire a new access token after the token has expired. - /// - public TimeSpan AccessTokenLifetime { get; set; } = TimeSpan.FromHours(1); - - /// - /// The period of time the identity token remains valid after being issued. The default is 20 minutes. - /// The client application is expected to refresh or acquire a new identity token after the token has expired. - /// - public TimeSpan IdentityTokenLifetime { get; set; } = TimeSpan.FromMinutes(20); - - /// - /// The period of time the refresh token remains valid after being issued. The default is 6 hours. - /// The client application is expected to start a whole new authentication flow after the refresh token has expired. - /// - public TimeSpan RefreshTokenLifetime { get; set; } = TimeSpan.FromHours(6); - - /// - /// Determines whether refresh tokens issued during a grant_type=refresh_token request should be generated - /// with a new expiration date or should re-use the same expiration date as the original refresh token. - /// Set this property to true to assign a new expiration date each time a refresh token is issued, - /// false to use the expiration date of the original refresh token. When set to false, - /// access and identity tokens' lifetime cannot exceed the expiration date of the refresh token. - /// - public bool UseSlidingExpiration { get; set; } = true; - - /// - /// Set to true if the web application is able to render error messages on the authorization endpoint. This is only needed for cases where - /// the browser is not redirected back to the client application, for example, when the client_id or redirect_uri are incorrect. The - /// authorization endpoint should expect to see the OpenID Connect response added to the ASP.NET 5 environment. - /// - public bool ApplicationCanDisplayErrors { get; set; } - - /// - /// True to allow incoming requests to arrive on HTTP and to allow redirect_uri parameters to have HTTP URI addresses. - /// Setting this option to false in production is strongly encouraged to mitigate man-in-the-middle attacks. - /// - public bool AllowInsecureHttp { get; set; } + public class OpenIddictOptions : OpenIdConnectServerOptions { + public OpenIddictOptions() { + AuthenticationScheme = OpenIddictDefaults.AuthenticationScheme; + ApplicationCanDisplayErrors = true; + } /// /// Set to true to allow you to use your own views/styles/scripts in your server. diff --git a/src/OpenIddict.Core/OpenIddictServices.cs b/src/OpenIddict.Core/OpenIddictServices.cs new file mode 100644 index 00000000..35bfa3e6 --- /dev/null +++ b/src/OpenIddict.Core/OpenIddictServices.cs @@ -0,0 +1,36 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/openiddict/core for more information concerning + * the license and the contributors participating to this project. + */ + +using System; +using Microsoft.Extensions.DependencyInjection; + +namespace OpenIddict { + public class OpenIddictServices { + public OpenIddictServices(IServiceCollection services) { + Services = services; + } + + /// + /// Gets or sets the type corresponding to the Application entity. + /// + public Type ApplicationType { get; set; } + + /// + /// Gets or sets the type corresponding to the Role entity. + /// + public Type RoleType { get; set; } + + /// + /// Gets or sets the type corresponding to the User entity. + /// + public Type UserType { get; set; } + + /// + /// Gets the services used by OpenIddict. + /// + public IServiceCollection Services { get; } + } +} \ No newline at end of file diff --git a/src/OpenIddict.EF/OpenIddictExtensions.cs b/src/OpenIddict.EF/OpenIddictExtensions.cs index e136dd63..9a826e61 100644 --- a/src/OpenIddict.EF/OpenIddictExtensions.cs +++ b/src/OpenIddict.EF/OpenIddictExtensions.cs @@ -16,21 +16,21 @@ using OpenIddict; namespace Microsoft.AspNet.Builder { public static class OpenIddictExtensions { - public static OpenIddictBuilder AddEntityFrameworkStore([NotNull] this OpenIddictBuilder builder) { - builder.Services.AddScoped( - typeof(IOpenIddictStore<,>).MakeGenericType(builder.UserType, builder.ApplicationType), + public static OpenIddictServices AddEntityFrameworkStore([NotNull] this OpenIddictServices services) { + services.Services.AddScoped( + typeof(IOpenIddictStore<,>).MakeGenericType(services.UserType, services.ApplicationType), typeof(OpenIddictStore<,,,,>).MakeGenericType( - /* TUser: */ builder.UserType, - /* TApplication: */ builder.ApplicationType, - /* TRole: */ builder.RoleType, - /* TContext: */ ResolveContextType(builder), - /* TKey: */ ResolveKeyType(builder))); + /* TUser: */ services.UserType, + /* TApplication: */ services.ApplicationType, + /* TRole: */ services.RoleType, + /* TContext: */ ResolveContextType(services), + /* TKey: */ ResolveKeyType(services))); - return builder; + return services; } - private static Type ResolveContextType([NotNull] OpenIddictBuilder builder) { - var service = (from registration in builder.Services + private static Type ResolveContextType([NotNull] OpenIddictServices services) { + var service = (from registration in services.Services where registration.ServiceType.IsConstructedGenericType let definition = registration.ServiceType.GetGenericTypeDefinition() where definition == typeof(IUserStore<>) @@ -65,9 +65,9 @@ namespace Microsoft.AspNet.Builder { throw new InvalidOperationException("The type of the database context cannot be automatically inferred."); } - private static Type ResolveKeyType([NotNull] OpenIddictBuilder builder) { + private static Type ResolveKeyType([NotNull] OpenIddictServices services) { TypeInfo type; - for (type = builder.UserType.GetTypeInfo(); type != null; type = type.BaseType?.GetTypeInfo()) { + for (type = services.UserType.GetTypeInfo(); type != null; type = type.BaseType?.GetTypeInfo()) { if (!type.IsGenericType) { continue; } @@ -86,7 +86,7 @@ namespace Microsoft.AspNet.Builder { throw new InvalidOperationException( "The type of the key identifier used by the user " + - $"entity '{builder.UserType}' cannot be automatically inferred."); + $"entity '{services.UserType}' cannot be automatically inferred."); } } } \ No newline at end of file diff --git a/src/OpenIddict/OpenIddictExtensions.cs b/src/OpenIddict/OpenIddictExtensions.cs index 7ac05a37..2fd23d82 100644 --- a/src/OpenIddict/OpenIddictExtensions.cs +++ b/src/OpenIddict/OpenIddictExtensions.cs @@ -4,6 +4,7 @@ * the license and the contributors participating to this project. */ +using System; using Microsoft.AspNet.Identity; using Microsoft.Extensions.Internal; using OpenIddict; @@ -11,12 +12,12 @@ using OpenIddict.Models; namespace Microsoft.AspNet.Builder { public static class OpenIddictExtensions { - public static OpenIddictBuilder AddOpenIddict([NotNull] this IdentityBuilder builder) { + public static OpenIddictServices AddOpenIddict([NotNull] this IdentityBuilder builder) { return builder.AddOpenIddictCore() .AddEntityFrameworkStore(); } - public static OpenIddictBuilder AddOpenIddict([NotNull] this IdentityBuilder builder) + public static OpenIddictServices AddOpenIddict([NotNull] this IdentityBuilder builder) where TApplication : Application { return builder.AddOpenIddictCore() .AddEntityFrameworkStore();