Browse Source

Migrate to the .NET Core/ASP.NET Core RTM packages

pull/147/head
Kévin Chalet 10 years ago
parent
commit
dced1cd8fe
  1. 2
      OpenIddict.sln
  2. 2
      global.json
  3. 2
      samples/Mvc.Client/Startup.cs
  4. 33
      samples/Mvc.Client/project.json
  5. 11
      samples/Mvc.Server/Startup.cs
  6. 40
      samples/Mvc.Server/project.json
  7. 4
      src/OpenIddict.Assets/project.json
  8. 22
      src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Authentication.cs
  9. 373
      src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Exchange.cs
  10. 6
      src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Revocation.cs
  11. 2
      src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs
  12. 37
      src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Userinfo.cs
  13. 3
      src/OpenIddict.Core/OpenIddictBuilder.cs
  14. 8
      src/OpenIddict.Core/project.json
  15. 2
      src/OpenIddict.EntityFramework/project.json
  16. 14
      src/OpenIddict.Mvc/OpenIddictController.cs
  17. 61
      src/OpenIddict.Mvc/OpenIddictExtensions.cs
  18. 13
      src/OpenIddict.Mvc/project.json
  19. 8
      src/OpenIddict.Security/project.json

2
OpenIddict.sln

@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14 # Visual Studio 14
VisualStudioVersion = 14.0.25123.0 VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "OpenIddict", "src\OpenIddict\OpenIddict.xproj", "{80A8D6CE-C29A-4602-9844-D51FEF9C33C8}" Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "OpenIddict", "src\OpenIddict\OpenIddict.xproj", "{80A8D6CE-C29A-4602-9844-D51FEF9C33C8}"
EndProject EndProject

2
global.json

@ -1,3 +1,3 @@
{ {
"projects": [ "src", "external" ] "projects": [ "src" ]
} }

2
samples/Mvc.Client/Startup.cs

@ -39,7 +39,7 @@ namespace Mvc.Client {
SaveTokens = true, SaveTokens = true,
// Use the authorization code flow. // Use the authorization code flow.
ResponseType = OpenIdConnectResponseTypes.Code, ResponseType = OpenIdConnectResponseType.Code,
// Note: setting the Authority allows the OIDC client middleware to automatically // Note: setting the Authority allows the OIDC client middleware to automatically
// retrieve the identity provider's configuration and spare you from setting // retrieve the identity provider's configuration and spare you from setting

33
samples/Mvc.Client/project.json

@ -14,30 +14,30 @@
}, },
"dependencies": { "dependencies": {
"Microsoft.AspNetCore.Authentication.Cookies": "1.0.0-rc2-final", "Microsoft.AspNetCore.Authentication.Cookies": "1.0.0",
"Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.0.0-rc2-final", "Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.0.0",
"Microsoft.AspNetCore.Diagnostics": "1.0.0-rc2-final", "Microsoft.AspNetCore.Diagnostics": "1.0.0",
"Microsoft.AspNetCore.Hosting": "1.0.0-rc2-final", "Microsoft.AspNetCore.Hosting": "1.0.0",
"Microsoft.AspNetCore.Mvc": "1.0.0-rc2-final", "Microsoft.AspNetCore.Mvc": "1.0.0",
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0-rc2-final", "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0-rc2-final", "Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
"Microsoft.AspNetCore.StaticFiles": "1.0.0-rc2-final", "Microsoft.AspNetCore.StaticFiles": "1.0.0",
"Microsoft.Extensions.Configuration.CommandLine": "1.0.0-rc2-final", "Microsoft.Extensions.Configuration.CommandLine": "1.0.0",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0-rc2-final", "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0",
"Microsoft.Extensions.Logging.Console": "1.0.0-rc2-final", "Microsoft.Extensions.Logging.Console": "1.0.0",
"Microsoft.Extensions.Logging.Debug": "1.0.0-rc2-final" "Microsoft.Extensions.Logging.Debug": "1.0.0"
}, },
"frameworks": { "frameworks": {
"net451": { "net451": {
"dependencies": { "dependencies": {
"Microsoft.NETCore.Platforms": "1.0.1-rc2-24027" "Microsoft.NETCore.Platforms": "1.0.1"
} }
}, },
"netcoreapp1.0": { "netcoreapp1.0": {
"dependencies": { "dependencies": {
"Microsoft.NETCore.App": { "type": "platform", "version": "1.0.0-rc2-3002702" } "Microsoft.NETCore.App": { "type": "platform", "version": "1.0.0" }
}, },
"imports": [ "imports": [
@ -48,10 +48,7 @@
}, },
"tools": { "tools": {
"Microsoft.AspNetCore.Server.IISIntegration.Tools": { "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final"
"version": "1.0.0-preview1-final",
"imports": "portable-net45+wp80+win8+wpa81+dnxcore50"
}
}, },
"scripts": { "scripts": {

11
samples/Mvc.Server/Startup.cs

@ -1,6 +1,5 @@
using System; using System;
using System.Linq; using System.Linq;
using AspNet.Security.OAuth.GitHub;
using CryptoHelper; using CryptoHelper;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
@ -63,9 +62,7 @@ namespace Mvc.Server {
// .SetLogoutEndpointPath("/connect/logout"); // .SetLogoutEndpointPath("/connect/logout");
// Note: if you don't explicitly register a signing key, one is automatically generated and // Note: if you don't explicitly register a signing key, one is automatically generated and
// persisted on the disk. If the key cannot be persisted, an in-memory key is used instead: // persisted on the disk. If the key cannot be persisted, an exception is thrown.
// when the application shuts down, the key is definitely lost and the access/identity tokens
// will be considered as invalid by client applications/resource servers when validating them.
// //
// On production, using a X.509 certificate stored in the machine store is recommended. // On production, using a X.509 certificate stored in the machine store is recommended.
// You can generate a self-signed certificate using Pluralsight's self-cert utility: // You can generate a self-signed certificate using Pluralsight's self-cert utility:
@ -121,11 +118,7 @@ namespace Mvc.Server {
ConsumerSecret = "Il2eFzGIrYhz6BWjYhVXBPQSfZuS4xoHpSSyD9PI" ConsumerSecret = "Il2eFzGIrYhz6BWjYhVXBPQSfZuS4xoHpSSyD9PI"
}); });
app.UseGitHubAuthentication(new GitHubAuthenticationOptions { app.UseSession();
ClientId = "49e302895d8b09ea5656",
ClientSecret = "98f1bf028608901e9df91d64ee61536fe562064b",
Scope = { "user:email" }
});
app.UseOpenIddict(); app.UseOpenIddict();

40
samples/Mvc.Server/project.json

@ -19,22 +19,21 @@
}, },
"dependencies": { "dependencies": {
"AspNet.Security.OAuth.GitHub": "1.0.0-alpha4-final", "AspNet.Security.OAuth.Introspection": "1.0.0-alpha2-final",
"AspNet.Security.OAuth.Introspection": "1.0.0-alpha1-final", "AspNet.Security.OAuth.Validation": "1.0.0-alpha2-final",
"AspNet.Security.OAuth.Validation": "1.0.0-alpha1-final", "Microsoft.AspNetCore.Authentication.Google": "1.0.0",
"Microsoft.AspNetCore.Authentication.Google": "1.0.0-rc2-final", "Microsoft.AspNetCore.Authentication.Twitter": "1.0.0",
"Microsoft.AspNetCore.Authentication.Twitter": "1.0.0-rc2-final", "Microsoft.AspNetCore.Diagnostics": "1.0.0",
"Microsoft.AspNetCore.Diagnostics": "1.0.0-rc2-final", "Microsoft.AspNetCore.Mvc": "1.0.0",
"Microsoft.AspNetCore.Mvc": "1.0.0-rc2-final", "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0-rc2-final", "Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0-rc2-final", "Microsoft.AspNetCore.StaticFiles": "1.0.0",
"Microsoft.AspNetCore.StaticFiles": "1.0.0-rc2-final", "Microsoft.EntityFrameworkCore.SqlServer": "1.0.0",
"Microsoft.EntityFrameworkCore.SqlServer": "1.0.0-rc2-final", "Microsoft.Extensions.Configuration.CommandLine": "1.0.0",
"Microsoft.Extensions.Configuration.CommandLine": "1.0.0-rc2-final", "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0-rc2-final", "Microsoft.Extensions.Configuration.Json": "1.0.0",
"Microsoft.Extensions.Configuration.Json": "1.0.0-rc2-final", "Microsoft.Extensions.Logging.Console": "1.0.0",
"Microsoft.Extensions.Logging.Console": "1.0.0-rc2-final", "Microsoft.Extensions.Logging.Debug": "1.0.0",
"Microsoft.Extensions.Logging.Debug": "1.0.0-rc2-final",
"OpenIddict": { "target": "project" }, "OpenIddict": { "target": "project" },
"OpenIddict.Assets": { "target": "project" }, "OpenIddict.Assets": { "target": "project" },
"OpenIddict.Mvc": { "target": "project" }, "OpenIddict.Mvc": { "target": "project" },
@ -44,13 +43,13 @@
"frameworks": { "frameworks": {
"net451": { "net451": {
"dependencies": { "dependencies": {
"Microsoft.NETCore.Platforms": "1.0.1-rc2-24027" "Microsoft.NETCore.Platforms": "1.0.1"
} }
}, },
"netcoreapp1.0": { "netcoreapp1.0": {
"dependencies": { "dependencies": {
"Microsoft.NETCore.App": { "type": "platform", "version": "1.0.0-rc2-3002702" } "Microsoft.NETCore.App": { "type": "platform", "version": "1.0.0" }
}, },
"imports": [ "imports": [
@ -61,10 +60,7 @@
}, },
"tools": { "tools": {
"Microsoft.AspNetCore.Server.IISIntegration.Tools": { "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final"
"version": "1.0.0-preview1-final",
"imports": "portable-net45+wp80+win8+wpa81+dnxcore50"
}
}, },
"scripts": { "scripts": {

4
src/OpenIddict.Assets/project.json

@ -42,8 +42,8 @@
"dependencies": { "dependencies": {
"JetBrains.Annotations": { "type": "build", "version": "10.1.4" }, "JetBrains.Annotations": { "type": "build", "version": "10.1.4" },
"Microsoft.AspNetCore.StaticFiles": "1.0.0-rc2-final", "Microsoft.AspNetCore.StaticFiles": "1.0.0",
"Microsoft.Extensions.FileProviders.Embedded": "1.0.0-rc2-final", "Microsoft.Extensions.FileProviders.Embedded": "1.0.0",
"OpenIddict.Core": { "target": "project" } "OpenIddict.Core": { "target": "project" }
}, },

22
src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Authentication.cs

@ -100,8 +100,8 @@ namespace OpenIddict.Infrastructure {
if (!string.IsNullOrEmpty(email) && string.Equals(username, email, StringComparison.OrdinalIgnoreCase)) { if (!string.IsNullOrEmpty(email) && string.Equals(username, email, StringComparison.OrdinalIgnoreCase)) {
services.Logger.LogError("The authorization request was rejected because the 'email' scope was not requested: " + services.Logger.LogError("The authorization request was rejected because the 'email' scope was not requested: " +
"to prevent data leakage, the 'email' scope must be granted when the username" + "to prevent data leakage, the 'email' scope must be granted when the username " +
"is identical to the email address associated with the user ({Username}).", username); "is identical to the email address associated with the user profile.");
context.Reject( context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest, error: OpenIdConnectConstants.Errors.InvalidRequest,
@ -178,19 +178,25 @@ namespace OpenIddict.Infrastructure {
// Note: user may be null if the user was removed after // Note: user may be null if the user was removed after
// the initial check made by ValidateAuthorizationRequest. // the initial check made by ValidateAuthorizationRequest.
// In this case, throw an exception to abort the request.
var user = await services.Users.GetUserAsync(principal); var user = await services.Users.GetUserAsync(principal);
if (user == null) { if (user == null) {
throw new InvalidOperationException("The prompt=none authorization request was aborted because the profile " + services.Logger.LogError("The authorization request was aborted because the profile corresponding " +
"corresponding to the logged in user was not found in the database."); "to the logged in user was not found in the database: {Identifier}.",
context.HttpContext.User.GetClaim(ClaimTypes.NameIdentifier));
context.Reject(
error: OpenIdConnectConstants.Errors.ServerError,
description: "An internal error has occurred.");
return;
} }
// Note: filtering the username is not needed at this stage as OpenIddictController.Accept // Note: filtering the username is not needed at this stage as OpenIddictController.Accept
// and OpenIddictProvider.GrantResourceOwnerCredentials are expected to reject requests that // and OpenIddictProvider.HandleTokenRequest are expected to reject requests that don't
// don't include the "email" scope if the username corresponds to the registed email address. // include the "email" scope if the username corresponds to the registed email address.
var identity = await services.Users.CreateIdentityAsync(user, context.Request.GetScopes()); var identity = await services.Users.CreateIdentityAsync(user, context.Request.GetScopes());
if (identity == null) { if (identity == null) {
throw new InvalidOperationException("The authorization request failed because the user manager returned a null " + throw new InvalidOperationException("The authorization request was aborted because the user manager returned a null " +
$"identity for user '{await services.Users.GetUserNameAsync(user)}'."); $"identity for user '{await services.Users.GetUserNameAsync(user)}'.");
} }

373
src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Exchange.cs

@ -59,9 +59,9 @@ namespace OpenIddict.Infrastructure {
// and https://tools.ietf.org/html/rfc6749#section-6 for more information. // and https://tools.ietf.org/html/rfc6749#section-6 for more information.
// Skip client authentication if the client identifier is missing. // Skip client authentication if the client identifier is missing.
// Note: ASOS will automatically ensure that the calling application // Note: the OpenID Connect server middleware will automatically ensure that
// cannot use an authorization code or a refresh token if it's not // the calling application cannot use an authorization code or a refresh token
// the intended audience, even if client authentication was skipped. // if it's not the intended audience, even if client authentication was skipped.
if (string.IsNullOrEmpty(context.ClientId)) { if (string.IsNullOrEmpty(context.ClientId)) {
services.Logger.LogInformation("The token request validation process was skipped " + services.Logger.LogInformation("The token request validation process was skipped " +
"because the client_id parameter was missing or empty."); "because the client_id parameter was missing or empty.");
@ -126,236 +126,263 @@ namespace OpenIddict.Infrastructure {
context.Validate(); context.Validate();
} }
public override async Task GrantClientCredentials([NotNull] GrantClientCredentialsContext context) { public override async Task HandleTokenRequest([NotNull] HandleTokenRequestContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TUser, TApplication, TAuthorization, TScope, TToken>>(); var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TUser, TApplication, TAuthorization, TScope, TToken>>();
// Retrieve the application details corresponding to the requested client_id. Debug.Assert(context.Request.IsAuthorizationCodeGrantType() ||
// Note: this call shouldn't return a null instance, but a race condition may occur context.Request.IsClientCredentialsGrantType() ||
// if the application was removed after the initial check made by ValidateTokenRequest. context.Request.IsPasswordGrantType() ||
var application = await services.Applications.FindByClientIdAsync(context.ClientId); context.Request.IsRefreshTokenGrantType(), "The grant_type parameter should be a supported value.");
if (application == null) {
throw new InvalidOperationException("The token request was aborted because the client application corresponding " +
$"to the '{context.ClientId}' identifier was not found in the database.");
}
var identity = new ClaimsIdentity(context.Options.AuthenticationScheme); // Note: the OpenID Connect server middleware automatically reuses the authentication ticket
// stored in the authorization code to create a new identity. To ensure the user was not removed
// after the authorization code was issued, a new check is made before validating the request.
if (context.Request.IsAuthorizationCodeGrantType()) {
Debug.Assert(context.Ticket.Principal != null, "The authentication ticket shouldn't be null.");
// Note: the name identifier is always included in both identity and var user = await services.Users.GetUserAsync(context.Ticket.Principal);
// access tokens, even if an explicit destination is not specified. if (user == null) {
identity.AddClaim(ClaimTypes.NameIdentifier, context.ClientId); services.Logger.LogError("The token request was rejected because the user profile associated " +
"with the authorization code was not found in the database: '{Identifier}'.",
context.Ticket.Principal.GetClaim(ClaimTypes.NameIdentifier));
identity.AddClaim(ClaimTypes.Name, await services.Applications.GetDisplayNameAsync(application), context.Reject(
OpenIdConnectConstants.Destinations.AccessToken, error: OpenIdConnectConstants.Errors.InvalidGrant,
OpenIdConnectConstants.Destinations.IdentityToken); description: "The authorization code is no longer valid.");
// Create a new authentication ticket return;
// holding the application identity. }
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
context.Options.AuthenticationScheme);
ticket.SetResources(context.Request.GetResources()); context.Validate(context.Ticket);
ticket.SetScopes(context.Request.GetScopes()); }
context.Validate(ticket); // Note: the OpenID Connect server middleware automatically reuses the authentication ticket
} // stored in the refresh token to create a new identity. To ensure the user was not removed
// after the refresh token was issued, a new check is made before validating the request.
else if (context.Request.IsRefreshTokenGrantType()) {
Debug.Assert(context.Ticket.Principal != null, "The authentication ticket shouldn't be null.");
public override async Task GrantAuthorizationCode([NotNull] GrantAuthorizationCodeContext context) { var user = await services.Users.GetUserAsync(context.Ticket.Principal);
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TUser, TApplication, TAuthorization, TScope, TToken>>(); if (user == null) {
services.Logger.LogError("The token request was rejected because the user profile associated " +
"with the refresh token was not found in the database: '{Identifier}'.",
context.Ticket.Principal.GetClaim(ClaimTypes.NameIdentifier));
var user = await services.Users.GetUserAsync(context.Ticket.Principal); context.Reject(
if (user == null) { error: OpenIdConnectConstants.Errors.InvalidGrant,
services.Logger.LogError("The token request was rejected because the user profile associated " + description: "The refresh token is no longer valid.");
"with the authorization code was not found in the database: '{Identifier}'.",
context.Ticket.Principal.GetClaim(ClaimTypes.NameIdentifier));
context.Reject( return;
error: OpenIdConnectConstants.Errors.InvalidGrant, }
description: "The authorization code is no longer valid.");
return; // Extract the token identifier from the refresh token.
} var identifier = context.Ticket.GetTicketId();
Debug.Assert(!string.IsNullOrEmpty(identifier),
"The refresh token should contain a ticket identifier.");
context.Validate(context.Ticket); // Retrieve the token from the database and ensure it is still valid.
} var token = await services.Tokens.FindByIdAsync(identifier);
if (token == null) {
services.Logger.LogError("The token request was rejected because the refresh token was revoked.");
public override async Task GrantRefreshToken([NotNull] GrantRefreshTokenContext context) { context.Reject(
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TUser, TApplication, TAuthorization, TScope, TToken>>(); error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "The refresh token is no longer valid.");
var user = await services.Users.GetUserAsync(context.Ticket.Principal); return;
if (user == null) { }
services.Logger.LogError("The token request was rejected because the user profile associated " +
"with the refresh token was not found in the database: '{Identifier}'.",
context.Ticket.Principal.GetClaim(ClaimTypes.NameIdentifier));
context.Reject( // When sliding expiration is enabled, immediately
error: OpenIdConnectConstants.Errors.InvalidGrant, // revoke the refresh token to prevent future reuse.
description: "The refresh token is no longer valid."); // See https://tools.ietf.org/html/rfc6749#section-6.
if (context.Options.UseSlidingExpiration) {
await services.Tokens.RevokeAsync(token);
}
return; // Note: the "scopes" property stored in context.AuthenticationTicket is automatically updated by the
// OpenID Connect server middleware when the client application requests a restricted scopes collection.
var identity = await services.Users.CreateIdentityAsync(user, context.Ticket.GetScopes());
if (identity == null) {
throw new InvalidOperationException("The token request was aborted because the user manager returned a null " +
$"identity for user '{await services.Users.GetUserNameAsync(user)}'.");
}
// Create a new authentication ticket holding the user identity but
// reuse the authentication properties stored in the refresh token.
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
context.Ticket.Properties,
context.Options.AuthenticationScheme);
context.Validate(ticket);
} }
// Extract the token identifier from the refresh token. else if (context.Request.IsPasswordGrantType()) {
var identifier = context.Ticket.GetTicketId(); // Note: at this stage, the client credentials cannot be null as the OpenID Connect server middleware
Debug.Assert(!string.IsNullOrEmpty(identifier), // automatically rejects grant_type=password requests that don't specify a username/password couple.
"The refresh token should contain a ticket identifier."); Debug.Assert(!string.IsNullOrEmpty(context.Request.Username) &&
!string.IsNullOrEmpty(context.Request.Password), "The user credentials shouldn't be null.");
// Retrieve the token from the database and ensure it is still valid. var user = await services.Users.FindByNameAsync(context.Request.Username);
var token = await services.Tokens.FindByIdAsync(identifier); if (user == null) {
if (token == null) { services.Logger.LogError("The token request was rejected because no user profile corresponding to " +
services.Logger.LogError("The token request was rejected because the refresh token was revoked."); "the specified username was found: '{Username}'.", context.Request.Username);
context.Reject( context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant, error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "The refresh token is no longer valid."); description: "Invalid credentials.");
return; return;
} }
// When sliding expiration is enabled, immediately // Ensure the user is allowed to sign in.
// revoke the refresh token to prevent future reuse. if (!await services.SignIn.CanSignInAsync(user)) {
// See https://tools.ietf.org/html/rfc6749#section-6. services.Logger.LogError("The token request was rejected because the user '{Username}' " +
if (context.Options.UseSlidingExpiration) { "was not allowed to sign in.", context.Request.Username);
await services.Tokens.RevokeAsync(token);
}
// Note: the "scopes" property stored in context.AuthenticationTicket is automatically context.Reject(
// updated by ASOS when the client application requests a restricted scopes collection. error: OpenIdConnectConstants.Errors.InvalidGrant,
var identity = await services.Users.CreateIdentityAsync(user, context.Ticket.GetScopes()); description: "The user is not allowed to sign in.");
if (identity == null) {
throw new InvalidOperationException("The token request failed because the user manager returned a null " +
$"identity for user '{await services.Users.GetUserNameAsync(user)}'.");
}
// Create a new authentication ticket holding the user identity but return;
// reuse the authentication properties stored in the refresh token. }
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
context.Ticket.Properties,
context.Options.AuthenticationScheme);
context.Validate(ticket); // Ensure the user is not already locked out.
} if (services.Users.SupportsUserLockout && await services.Users.IsLockedOutAsync(user)) {
services.Logger.LogError("The token request was rejected because the account '{Username}' " +
"was locked out to prevent brute force attacks.", context.Request.Username);
public override async Task GrantResourceOwnerCredentials([NotNull] GrantResourceOwnerCredentialsContext context) { context.Reject(
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TUser, TApplication, TAuthorization, TScope, TToken>>(); error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "Account locked out.");
var user = await services.Users.FindByNameAsync(context.UserName); return;
if (user == null) { }
services.Logger.LogError("The token request was rejected because no user profile corresponding to " +
"the specified username was found: '{Username}'.", context.UserName);
context.Reject( // Ensure the password is valid.
error: OpenIdConnectConstants.Errors.InvalidGrant, if (!await services.Users.CheckPasswordAsync(user, context.Request.Password)) {
description: "Invalid credentials."); services.Logger.LogError("The token request was rejected because the password didn't match " +
"the password associated with the account '{Username}'.", context.Request.Username);
return; context.Reject(
} error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "Invalid credentials.");
// Ensure the user is allowed to sign in. if (services.Users.SupportsUserLockout) {
if (!await services.SignIn.CanSignInAsync(user)) { await services.Users.AccessFailedAsync(user);
services.Logger.LogError("The token request was rejected because the user '{Username}' " +
"was not allowed to sign in.", context.UserName);
context.Reject( // Ensure the user is not locked out.
error: OpenIdConnectConstants.Errors.InvalidGrant, if (await services.Users.IsLockedOutAsync(user)) {
description: "The user is not allowed to sign in."); services.Logger.LogError("The token request was rejected because the account '{Username}' " +
"was locked out to prevent brute force attacks.", context.Request.Username);
return; context.Reject(
} error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "Account locked out.");
}
}
// Ensure the user is not already locked out. return;
if (services.Users.SupportsUserLockout && await services.Users.IsLockedOutAsync(user)) { }
services.Logger.LogError("The token request was rejected because the account '{Username}' " +
"was locked out to prevent brute force attacks.", context.UserName);
context.Reject( if (services.Users.SupportsUserLockout) {
error: OpenIdConnectConstants.Errors.InvalidGrant, await services.Users.ResetAccessFailedCountAsync(user);
description: "Account locked out."); }
return; // Reject the token request if two-factor authentication has been enabled by the user.
} if (services.Users.SupportsUserTwoFactor && await services.Users.GetTwoFactorEnabledAsync(user)) {
services.Logger.LogError("The token request was rejected because two-factor authentication " +
"was required for the account '{Username}.", context.Request.Username);
// Ensure the password is valid. context.Reject(
if (!await services.Users.CheckPasswordAsync(user, context.Password)) { error: OpenIdConnectConstants.Errors.InvalidGrant,
services.Logger.LogError("The token request was rejected because the password didn't match " + description: "Two-factor authentication is required for this account.");
"the password associated with the account '{Username}'.", context.UserName);
context.Reject( return;
error: OpenIdConnectConstants.Errors.InvalidGrant, }
description: "Invalid credentials.");
if (services.Users.SupportsUserLockout) { // Return an error if the username corresponds to the registered
await services.Users.AccessFailedAsync(user); // email address and if the "email" scope has not been requested.
if (services.Users.SupportsUserEmail && context.Request.HasScope(OpenIdConnectConstants.Scopes.Profile) &&
!context.Request.HasScope(OpenIdConnectConstants.Scopes.Email)) {
// Retrieve the username and the email address associated with the user.
var username = await services.Users.GetUserNameAsync(user);
var email = await services.Users.GetEmailAsync(user);
// Ensure the user is not locked out. if (!string.IsNullOrEmpty(email) && string.Equals(username, email, StringComparison.OrdinalIgnoreCase)) {
if (await services.Users.IsLockedOutAsync(user)) { services.Logger.LogError("The token request was rejected because the 'email' scope was not requested: " +
services.Logger.LogError("The token request was rejected because the account '{Username}' " + "to prevent data leakage, the 'email' scope must be granted when the username " +
"was locked out to prevent brute force attacks.", context.UserName); "is identical to the email address associated with the user profile.");
context.Reject( context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant, error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "Account locked out."); description: "The 'email' scope is required.");
return;
} }
} }
return; var identity = await services.Users.CreateIdentityAsync(user, context.Request.GetScopes());
} if (identity == null) {
throw new InvalidOperationException("The token request was aborted because the user manager returned a null " +
if (services.Users.SupportsUserLockout) { $"identity for user '{await services.Users.GetUserNameAsync(user)}'.");
await services.Users.ResetAccessFailedCountAsync(user); }
}
// Reject the token request if two-factor authentication has been enabled by the user. // Create a new authentication ticket holding the user identity.
if (services.Users.SupportsUserTwoFactor && await services.Users.GetTwoFactorEnabledAsync(user)) { var ticket = new AuthenticationTicket(
services.Logger.LogError("The token request was rejected because two-factor authentication " + new ClaimsPrincipal(identity),
"was required for the account '{Username}.", context.UserName); new AuthenticationProperties(),
context.Options.AuthenticationScheme);
context.Reject( ticket.SetResources(context.Request.GetResources());
error: OpenIdConnectConstants.Errors.InvalidGrant, ticket.SetScopes(context.Request.GetScopes());
description: "Two-factor authentication is required for this account.");
return; context.Validate(ticket);
} }
// Return an error if the username corresponds to the registered else if (context.Request.IsClientCredentialsGrantType()) {
// email address and if the "email" scope has not been requested. // Note: at this stage, the client credentials cannot be null or invalid, as client authentication is required
if (services.Users.SupportsUserEmail && context.Request.HasScope(OpenIdConnectConstants.Scopes.Profile) && // to use the client credentials grant and is automatically enforced by the OpenID Connect server middleware.
!context.Request.HasScope(OpenIdConnectConstants.Scopes.Email)) { Debug.Assert(!string.IsNullOrEmpty(context.Request.ClientId) &&
// Retrieve the username and the email address associated with the user. !string.IsNullOrEmpty(context.Request.ClientSecret), "The client credentials shouldn't be null.");
var username = await services.Users.GetUserNameAsync(user);
var email = await services.Users.GetEmailAsync(user);
if (!string.IsNullOrEmpty(email) && string.Equals(username, email, StringComparison.OrdinalIgnoreCase)) { // Retrieve the application details corresponding to the requested client_id.
services.Logger.LogError("The token request was rejected because the 'email' scope was not requested: " + // Note: this call shouldn't return a null instance, but a race condition may occur
"to prevent data leakage, the 'email' scope must be granted when the username" + // if the application was removed after the initial check made by ValidateTokenRequest.
"is identical to the email address associated with the user ({Username}).", username); var application = await services.Applications.FindByClientIdAsync(context.Request.ClientId);
if (application == null) {
services.Logger.LogError("The token request was aborted because the client application " +
"was not found in the database: '{ClientId}'.", context.Request.ClientId);
context.Reject( context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest, error: OpenIdConnectConstants.Errors.InvalidClient,
description: "The 'email' scope is required."); description: "Application not found in the database: ensure that your client_id is correct.");
return; return;
} }
}
var identity = await services.Users.CreateIdentityAsync(user, context.Request.GetScopes()); var identity = new ClaimsIdentity(context.Options.AuthenticationScheme);
if (identity == null) {
throw new InvalidOperationException("The token request failed because the user manager returned a null " + // Note: the name identifier is always included in both identity and
$"identity for user '{await services.Users.GetUserNameAsync(user)}'."); // access tokens, even if an explicit destination is not specified.
} identity.AddClaim(ClaimTypes.NameIdentifier, context.Request.ClientId);
// Create a new authentication ticket holding the user identity. identity.AddClaim(ClaimTypes.Name, await services.Applications.GetDisplayNameAsync(application),
var ticket = new AuthenticationTicket( OpenIdConnectConstants.Destinations.AccessToken,
new ClaimsPrincipal(identity), OpenIdConnectConstants.Destinations.IdentityToken);
new AuthenticationProperties(),
context.Options.AuthenticationScheme);
ticket.SetResources(context.Request.GetResources()); // Create a new authentication ticket
ticket.SetScopes(context.Request.GetScopes()); // holding the application identity.
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
context.Options.AuthenticationScheme);
context.Validate(ticket); ticket.SetResources(context.Request.GetResources());
ticket.SetScopes(context.Request.GetScopes());
context.Validate(ticket);
}
} }
} }
} }

6
src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Revocation.cs

@ -32,9 +32,9 @@ namespace OpenIddict.Infrastructure {
} }
// Skip client authentication if the client identifier is missing. // Skip client authentication if the client identifier is missing.
// Note: ASOS will automatically ensure that the calling application // Note: the OpenID Connect server middleware will automatically ensure that
// cannot revoke a refresh token if it's not the intended audience, // the calling application cannot revoke a refresh token if it's not
// even if client authentication was skipped. // the intended audience, even if client authentication was skipped.
if (string.IsNullOrEmpty(context.ClientId)) { if (string.IsNullOrEmpty(context.ClientId)) {
services.Logger.LogInformation("The revocation request validation process was skipped " + services.Logger.LogInformation("The revocation request validation process was skipped " +
"because the client_id parameter was missing or empty."); "because the client_id parameter was missing or empty.");

2
src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs

@ -20,7 +20,7 @@ namespace OpenIddict.Infrastructure {
public override async Task SerializeRefreshToken([NotNull] SerializeRefreshTokenContext context) { public override async Task SerializeRefreshToken([NotNull] SerializeRefreshTokenContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TUser, TApplication, TAuthorization, TScope, TToken>>(); var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TUser, TApplication, TAuthorization, TScope, TToken>>();
Debug.Assert(context.Request.RequestType == OpenIdConnectRequestType.TokenRequest, Debug.Assert(context.Request.RequestType == OpenIdConnectRequestType.Token,
"The request should be a token request."); "The request should be a token request.");
Debug.Assert(!context.Request.IsClientCredentialsGrantType(), Debug.Assert(!context.Request.IsClientCredentialsGrantType(),

37
src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Userinfo.cs

@ -4,8 +4,6 @@
* the license and the contributors participating to this project. * the license and the contributors participating to this project.
*/ */
using System;
using System.Diagnostics;
using System.Security.Claims; using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions; using AspNet.Security.OpenIdConnect.Extensions;
@ -18,18 +16,13 @@ using Newtonsoft.Json.Linq;
namespace OpenIddict.Infrastructure { namespace OpenIddict.Infrastructure {
public partial class OpenIddictProvider<TUser, TApplication, TAuthorization, TScope, TToken> : OpenIdConnectServerProvider public partial class OpenIddictProvider<TUser, TApplication, TAuthorization, TScope, TToken> : OpenIdConnectServerProvider
where TUser : class where TApplication : class where TAuthorization : class where TScope : class where TToken : class { where TUser : class where TApplication : class where TAuthorization : class where TScope : class where TToken : class {
public override async Task ValidateUserinfoRequest([NotNull] ValidateUserinfoRequestContext context) { public override async Task HandleUserinfoRequest([NotNull] HandleUserinfoRequestContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TUser, TApplication, TAuthorization, TScope, TToken>>(); var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TUser, TApplication, TAuthorization, TScope, TToken>>();
// Note: the principal returned by AuthenticateAsync cannot be null as the OpenID Connect server // Note: user may be null if the user was removed after the access token was issued.
// middleware always ensures the ticket is valid before invoking the ValidateUserinfoRequest event. var user = await services.Users.GetUserAsync(context.Ticket.Principal);
var principal = await context.HttpContext.Authentication.AuthenticateAsync(context.Options.AuthenticationScheme);
Debug.Assert(principal != null, "The principal extracted from the access token shouldn't be null.");
// Ensure the user was not removed from the database.
var user = await services.Users.GetUserAsync(principal);
if (user == null) { if (user == null) {
services.Logger.LogError("The userinfo request was rejected because the user profile " + services.Logger.LogError("The userinfo request was aborted because the user profile " +
"corresponding to the access token was not found in the database."); "corresponding to the access token was not found in the database.");
context.Reject( context.Reject(
@ -39,29 +32,14 @@ namespace OpenIddict.Infrastructure {
return; return;
} }
context.Validate();
}
public override async Task HandleUserinfoRequest([NotNull] HandleUserinfoRequestContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TUser, TApplication, TAuthorization, TScope, TToken>>();
// Note: user may be null if the user was removed after
// the initial check made by ValidateUserinfoRequest.
// In this case, throw an exception to abort the request.
var user = await services.Users.GetUserAsync(context.Ticket.Principal);
if (user == null) {
throw new InvalidOperationException("The userinfo request was aborted because the user profile " +
"corresponding to the access token was not found in the database.");
}
// Note: "sub" is a mandatory claim. // Note: "sub" is a mandatory claim.
// See http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse // See http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
context.Subject = await services.Users.GetUserIdAsync(user); context.Subject = await services.Users.GetUserIdAsync(user);
// Only add the "preferred_username" claim if the "profile" scope was present in the access token. // Only add the "preferred_username" claim if the "profile" scope was present in the access token.
// Note: filtering the username is not needed at this stage as OpenIddictController.Accept // Note: filtering the username is not needed at this stage as OpenIddictController.Accept
// and OpenIddictProvider.GrantResourceOwnerCredentials are expected to reject requests that // and OpenIddictProvider.HandleTokenRequest are expected to reject requests that don't
// don't include the "email" scope if the username corresponds to the registered email address. // include the "email" scope if the username corresponds to the registed email address.
if (context.Ticket.HasScope(OpenIdConnectConstants.Scopes.Profile)) { if (context.Ticket.HasScope(OpenIdConnectConstants.Scopes.Profile)) {
context.PreferredUsername = await services.Users.GetUserNameAsync(user); context.PreferredUsername = await services.Users.GetUserNameAsync(user);
@ -84,8 +62,7 @@ namespace OpenIddict.Infrastructure {
}; };
// Only add the phone number details if the "phone" scope was present in the access token. // Only add the phone number details if the "phone" scope was present in the access token.
if (services.Users.SupportsUserPhoneNumber && if (services.Users.SupportsUserPhoneNumber && context.Ticket.HasScope(OpenIdConnectConstants.Scopes.Phone)) {
context.Ticket.HasScope(OpenIdConnectConstants.Scopes.Phone)) {
context.PhoneNumber = await services.Users.GetPhoneNumberAsync(user); context.PhoneNumber = await services.Users.GetPhoneNumberAsync(user);
// Only add the "phone_number_verified" // Only add the "phone_number_verified"

3
src/OpenIddict.Core/OpenIddictBuilder.cs

@ -6,6 +6,7 @@
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.IdentityModel.Tokens.Jwt;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -431,7 +432,7 @@ namespace Microsoft.AspNetCore.Builder {
/// </summary> /// </summary>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns> /// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder UseJsonWebTokens() { public virtual OpenIddictBuilder UseJsonWebTokens() {
return Configure(options => options.UseJwtTokens()); return Configure(options => options.AccessTokenHandler = new JwtSecurityTokenHandler());
} }
} }
} }

8
src/OpenIddict.Core/project.json

@ -33,11 +33,11 @@
}, },
"dependencies": { "dependencies": {
"AspNet.Security.OpenIdConnect.Server": "1.0.0-beta5-final", "AspNet.Security.OpenIdConnect.Server": "1.0.0-beta6-final",
"CryptoHelper": "1.0.0-rc2-final", "CryptoHelper": "2.0.0",
"JetBrains.Annotations": { "type": "build", "version": "10.1.4" }, "JetBrains.Annotations": { "type": "build", "version": "10.1.4" },
"Microsoft.AspNetCore.Identity": "1.0.0-rc2-final", "Microsoft.AspNetCore.Identity": "1.0.0",
"Microsoft.Extensions.Caching.Memory": "1.0.0-rc2-final" "Microsoft.Extensions.Caching.Memory": "1.0.0"
}, },
"frameworks": { "frameworks": {

2
src/OpenIddict.EntityFramework/project.json

@ -34,7 +34,7 @@
"dependencies": { "dependencies": {
"JetBrains.Annotations": { "type": "build", "version": "10.1.4" }, "JetBrains.Annotations": { "type": "build", "version": "10.1.4" },
"Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.0.0-rc2-final", "Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.0.0",
"OpenIddict.Core": { "target": "project" } "OpenIddict.Core": { "target": "project" }
}, },

14
src/OpenIddict.Mvc/OpenIddictController.cs

@ -54,7 +54,7 @@ namespace OpenIddict.Mvc {
}); });
} }
// Note: AspNet.Security.OpenIdConnect.Server automatically ensures an application // Note: the OpenID Connect server middleware automatically ensures an application
// corresponds to the client_id specified in the authorization request using // corresponds to the client_id specified in the authorization request using
// IOpenIdConnectServerProvider.ValidateAuthorizationRequest (see OpenIddictProvider.cs). // IOpenIdConnectServerProvider.ValidateAuthorizationRequest (see OpenIddictProvider.cs).
var application = await applications.FindByClientIdAsync(request.ClientId); var application = await applications.FindByClientIdAsync(request.ClientId);
@ -117,7 +117,7 @@ namespace OpenIddict.Mvc {
ticket.SetResources(request.GetResources()); ticket.SetResources(request.GetResources());
ticket.SetScopes(request.GetScopes()); ticket.SetScopes(request.GetScopes());
// Returning a SignInResult will ask ASOS to serialize the specified identity to build appropriate tokens. // Returning a SignInResult will ask the OpenID Connect server middleware to issue the appropriate tokens.
// Note: you should always make sure the identities you return contain ClaimTypes.NameIdentifier claim. // Note: you should always make sure the identities you return contain ClaimTypes.NameIdentifier claim.
// In this sample, the identity always contains the name identifier returned by the external provider. // In this sample, the identity always contains the name identifier returned by the external provider.
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme); return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
@ -138,9 +138,9 @@ namespace OpenIddict.Mvc {
})); }));
} }
// Notify ASOS that the authorization grant has been denied by the resource owner. // Notify the OpenID Connect server middleware that the authorization grant
// Note: OpenIdConnectServerHandler will automatically take care of redirecting // has been denied by the resource owner to redirect user agent to
// the user agent to the client application using the appropriate response_mode. // the client application using the appropriate response_mode.
return Task.FromResult<IActionResult>(Forbid(options.Value.AuthenticationScheme)); return Task.FromResult<IActionResult>(Forbid(options.Value.AuthenticationScheme));
} }
@ -171,8 +171,8 @@ namespace OpenIddict.Mvc {
// after a successful authentication flow (e.g Google or Facebook). // after a successful authentication flow (e.g Google or Facebook).
await signin.SignOutAsync(); await signin.SignOutAsync();
// Returning a SignOutResult will ask ASOS to redirect the user agent // Returning a SignOutResult will ask the OpenID Connect server middleware to redirect
// to the post_logout_redirect_uri specified by the client application. // the user agent to the post_logout_redirect_uri specified by the client application.
return SignOut(options.Value.AuthenticationScheme); return SignOut(options.Value.AuthenticationScheme);
} }
} }

61
src/OpenIddict.Mvc/OpenIddictExtensions.cs

@ -7,14 +7,17 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using System.Reflection; using System.Reflection;
using AspNet.Security.OpenIdConnect.Server; using AspNet.Security.OpenIdConnect.Server;
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyModel;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using OpenIddict; using OpenIddict;
@ -81,8 +84,10 @@ namespace Microsoft.AspNetCore.Builder {
// Note: ConfigureApplicationPartManager() must be // Note: ConfigureApplicationPartManager() must be
// called before AddControllersAsServices(). // called before AddControllersAsServices().
.ConfigureApplicationPartManager(manager => { .ConfigureApplicationPartManager(manager => {
var parts = manager.ApplicationParts.ToArray();
manager.ApplicationParts.Clear(); manager.ApplicationParts.Clear();
manager.ApplicationParts.Add(new OpenIddictPart(builder)); manager.ApplicationParts.Add(new OpenIddictPart(builder, parts));
}) })
.AddControllersAsServices() .AddControllersAsServices()
@ -169,30 +174,64 @@ namespace Microsoft.AspNetCore.Builder {
private class OpenIddictConvention : IControllerModelConvention { private class OpenIddictConvention : IControllerModelConvention {
public void Apply(ControllerModel controller) { public void Apply(ControllerModel controller) {
// Ensure the convention is only applied to the intended controller. // Ensure the convention is only applied to the intended controller.
Debug.Assert(controller.ControllerType != null); if (controller.ControllerType != null &&
Debug.Assert(controller.ControllerType.IsGenericType); controller.ControllerType.IsGenericType &&
Debug.Assert(controller.ControllerType.GetGenericTypeDefinition() == typeof(OpenIddictController<,,,>)); controller.ControllerType.GetGenericTypeDefinition() == typeof(OpenIddictController<,,,>)) {
// Note: manually updating the controller name is required
// Note: manually updating the controller name is required // to remove the ending markers added to the generic type name.
// to remove the ending markers added to the generic type name. controller.ControllerName = "OpenIddict";
controller.ControllerName = "OpenIddict"; }
} }
} }
private class OpenIddictPart : ApplicationPart, IApplicationPartTypeProvider { private class OpenIddictPart : ApplicationPart, IApplicationPartTypeProvider, ICompilationReferencesProvider {
public OpenIddictPart(OpenIddictBuilder builder) { public OpenIddictPart(OpenIddictBuilder builder, IEnumerable<ApplicationPart> parts) {
Types = new[] { var types = new List<TypeInfo> {
typeof(OpenIddictController<,,,>).MakeGenericType( typeof(OpenIddictController<,,,>).MakeGenericType(
/* TUser: */ builder.UserType, /* TUser: */ builder.UserType,
/* TApplication: */ builder.ApplicationType, /* TApplication: */ builder.ApplicationType,
/* TAuthorization: */ builder.AuthorizationType, /* TAuthorization: */ builder.AuthorizationType,
/* TToken: */ builder.TokenType).GetTypeInfo() /* TToken: */ builder.TokenType).GetTypeInfo()
}; };
var assemblies = new List<Assembly>();
foreach (var part in parts.OfType<AssemblyPart>()) {
assemblies.Add(part.Assembly);
foreach (var type in part.Types) {
if (typeof(ControllerBase).GetTypeInfo().IsAssignableFrom(type)) {
continue;
}
types.Add(type);
}
}
Assemblies = assemblies;
Types = types;
} }
public override string Name { get; } = "OpenIddict.Mvc"; public override string Name { get; } = "OpenIddict.Mvc";
public IEnumerable<Assembly> Assemblies { get; }
public IEnumerable<TypeInfo> Types { get; } public IEnumerable<TypeInfo> Types { get; }
public IEnumerable<string> GetReferencePaths() {
foreach (var assembly in Assemblies) {
var context = DependencyContext.Load(assembly);
if (context == null) {
continue;
}
foreach (var library in context.CompileLibraries) {
foreach (var path in library.ResolveReferencePaths()) {
yield return path;
}
}
}
}
} }
} }
} }

13
src/OpenIddict.Mvc/project.json

@ -30,6 +30,7 @@
"warningsAsErrors": true, "warningsAsErrors": true,
"nowarn": [ "CS1591" ], "nowarn": [ "CS1591" ],
"xmlDoc": true, "xmlDoc": true,
"preserveCompilationContext": true,
"embed": { "embed": {
"include": [ "Views/**" ] "include": [ "Views/**" ]
@ -37,20 +38,20 @@
}, },
"dependencies": { "dependencies": {
"AspNet.Hosting.Extensions": "1.0.0-alpha2-final", "AspNet.Hosting.Extensions": "1.0.0-alpha3-final",
"JetBrains.Annotations": { "type": "build", "version": "10.1.4" }, "JetBrains.Annotations": { "type": "build", "version": "10.1.4" },
"Microsoft.AspNetCore.Mvc": "1.0.0-rc2-final", "Microsoft.AspNetCore.Mvc": "1.0.0",
"Microsoft.Extensions.FileProviders.Embedded": "1.0.0-rc2-final", "Microsoft.Extensions.FileProviders.Embedded": "1.0.0",
"Microsoft.Extensions.FileProviders.Composite": "1.0.0-rc2-final", "Microsoft.Extensions.FileProviders.Composite": "1.0.0",
"OpenIddict.Core": { "target": "project" } "OpenIddict.Core": { "target": "project" }
}, },
"frameworks": { "frameworks": {
"net451": { }, "net451": { },
"netstandard1.5": { "netstandard1.6": {
"imports": [ "imports": [
"dotnet5.6", "dotnet5.7",
"portable-net451+win8" "portable-net451+win8"
] ]
} }

8
src/OpenIddict.Security/project.json

@ -34,17 +34,17 @@
"dependencies": { "dependencies": {
"JetBrains.Annotations": { "type": "build", "version": "10.1.4" }, "JetBrains.Annotations": { "type": "build", "version": "10.1.4" },
"Microsoft.AspNetCore.Cors": "1.0.0-rc2-final", "Microsoft.AspNetCore.Cors": "1.0.0",
"NWebsec.AspNetCore.Middleware": "1.0.0-gamma-5", "NWebsec.AspNetCore.Middleware": "1.0.0-gamma1-15",
"OpenIddict.Core": { "target": "project" } "OpenIddict.Core": { "target": "project" }
}, },
"frameworks": { "frameworks": {
"net451": { }, "net451": { },
"netstandard1.5": { "netstandard1.4": {
"imports": [ "imports": [
"dotnet5.6", "dotnet5.5",
"portable-net451+win8" "portable-net451+win8"
] ]
} }

Loading…
Cancel
Save