Browse Source

Remove the internal CanSignInAsync/IsLockedOutAsync checks

pull/220/head
Kévin Chalet 10 years ago
parent
commit
12af8067f8
  1. 9
      README.md
  2. 130
      samples/Mvc.Server/Controllers/AuthorizationController.cs
  3. 5
      samples/Mvc.Server/Startup.cs
  4. 85
      src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Exchange.cs

9
README.md

@ -171,19 +171,14 @@ public async Task<IActionResult> Exchange() {
// Ensure the password is valid. // Ensure the password is valid.
if (!await _userManager.CheckPasswordAsync(user, request.Password)) { if (!await _userManager.CheckPasswordAsync(user, request.Password)) {
if (_userManager.SupportsUserLockout) {
await _userManager.AccessFailedAsync(user);
}
return BadRequest(new OpenIdConnectResponse { return BadRequest(new OpenIdConnectResponse {
Error = OpenIdConnectConstants.Errors.InvalidGrant, Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The username/password couple is invalid." ErrorDescription = "The username/password couple is invalid."
}); });
} }
if (_userManager.SupportsUserLockout) { // Note: for a more complete sample including account lockout support, visit
await _userManager.ResetAccessFailedCountAsync(user); // https://github.com/openiddict/openiddict-core/blob/dev/samples/Mvc.Server/Controllers/AuthorizationController.cs
}
var identity = await _userManager.CreateIdentityAsync(user, request.GetScopes()); var identity = await _userManager.CreateIdentityAsync(user, request.GetScopes());

130
samples/Mvc.Server/Controllers/AuthorizationController.cs

@ -36,6 +36,9 @@ namespace Mvc.Server {
_userManager = userManager; _userManager = userManager;
} }
// Note: to support interactive flows like the code flow,
// you must provide your own authorization endpoint action:
[Authorize, HttpGet, Route("~/connect/authorize")] [Authorize, HttpGet, Route("~/connect/authorize")]
public async Task<IActionResult> Authorize() { public async Task<IActionResult> Authorize() {
// Extract the authorization request from the ASP.NET environment. // Extract the authorization request from the ASP.NET environment.
@ -121,56 +124,81 @@ namespace Mvc.Server {
return SignOut(OpenIdConnectServerDefaults.AuthenticationScheme); return SignOut(OpenIdConnectServerDefaults.AuthenticationScheme);
} }
// Note: to support the password grant type, you must provide your own token endpoint action: // Note: to support non-interactive flows like password,
// you must provide your own token endpoint action:
// [HttpPost("~/connect/token")]
// [Produces("application/json")] [HttpPost("~/connect/token")]
// public async Task<IActionResult> Exchange() { [Produces("application/json")]
// var request = HttpContext.GetOpenIdConnectRequest(); public async Task<IActionResult> Exchange() {
// var request = HttpContext.GetOpenIdConnectRequest();
// if (request.IsPasswordGrantType()) {
// var user = await _userManager.FindByNameAsync(request.Username); if (request.IsPasswordGrantType()) {
// if (user == null) { var user = await _userManager.FindByNameAsync(request.Username);
// return BadRequest(new OpenIdConnectResponse { if (user == null) {
// Error = OpenIdConnectConstants.Errors.InvalidGrant, return BadRequest(new OpenIdConnectResponse {
// ErrorDescription = "The username/password couple is invalid." Error = OpenIdConnectConstants.Errors.InvalidGrant,
// }); ErrorDescription = "The username/password couple is invalid."
// } });
// }
// // Ensure the password is valid.
// if (!await _userManager.CheckPasswordAsync(user, request.Password)) { // Ensure the user is allowed to sign in.
// if (_userManager.SupportsUserLockout) { if (!await _signInManager.CanSignInAsync(user)) {
// await _userManager.AccessFailedAsync(user); return BadRequest(new OpenIdConnectResponse {
// } Error = OpenIdConnectConstants.Errors.InvalidGrant,
// ErrorDescription = "The specified user is not allowed to sign in."
// return BadRequest(new OpenIdConnectResponse { });
// Error = OpenIdConnectConstants.Errors.InvalidGrant, }
// ErrorDescription = "The username/password couple is invalid."
// }); // Reject the token request if two-factor authentication has been enabled by the user.
// } if (_userManager.SupportsUserTwoFactor && await _userManager.GetTwoFactorEnabledAsync(user)) {
// return BadRequest(new OpenIdConnectResponse {
// if (_userManager.SupportsUserLockout) { Error = OpenIdConnectConstants.Errors.InvalidGrant,
// await _userManager.ResetAccessFailedCountAsync(user); ErrorDescription = "The specified user is not allowed to sign in."
// } });
// }
// var identity = await _userManager.CreateIdentityAsync(user, request.GetScopes());
// // Ensure the user is not already locked out.
// // Create a new authentication ticket holding the user identity. if (_userManager.SupportsUserLockout && await _userManager.IsLockedOutAsync(user)) {
// var ticket = new AuthenticationTicket( return BadRequest(new OpenIdConnectResponse {
// new ClaimsPrincipal(identity), Error = OpenIdConnectConstants.Errors.InvalidGrant,
// new AuthenticationProperties(), ErrorDescription = "The username/password couple is invalid."
// OpenIdConnectServerDefaults.AuthenticationScheme); });
// }
// ticket.SetResources(request.GetResources());
// ticket.SetScopes(request.GetScopes()); // Ensure the password is valid.
// if (!await _userManager.CheckPasswordAsync(user, request.Password)) {
// return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme); if (_userManager.SupportsUserLockout) {
// } await _userManager.AccessFailedAsync(user);
// }
// return BadRequest(new OpenIdConnectResponse {
// Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, return BadRequest(new OpenIdConnectResponse {
// ErrorDescription = "The specified grant type is not supported." Error = OpenIdConnectConstants.Errors.InvalidGrant,
// }); ErrorDescription = "The username/password couple is invalid."
// } });
}
if (_userManager.SupportsUserLockout) {
await _userManager.ResetAccessFailedCountAsync(user);
}
var identity = await _userManager.CreateIdentityAsync(user, request.GetScopes());
// Create a new authentication ticket holding the user identity.
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetResources(request.GetResources());
ticket.SetScopes(request.GetScopes());
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
}
return BadRequest(new OpenIdConnectResponse {
Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
ErrorDescription = "The specified grant type is not supported."
});
}
} }
} }

5
samples/Mvc.Server/Startup.cs

@ -38,9 +38,10 @@ namespace Mvc.Server {
.EnableTokenEndpoint("/connect/token") .EnableTokenEndpoint("/connect/token")
.EnableUserinfoEndpoint("/connect/userinfo") .EnableUserinfoEndpoint("/connect/userinfo")
// Note: the Mvc.Client sample only uses the authorization code flow but you can enable // Note: the Mvc.Client sample only uses the code flow and the password flow, but you
// the other flows if you need to support implicit, password or client credentials. // can enable the other flows if you need to support implicit or client credentials.
.AllowAuthorizationCodeFlow() .AllowAuthorizationCodeFlow()
.AllowPasswordFlow()
.AllowRefreshTokenFlow() .AllowRefreshTokenFlow()
// During development, you can disable the HTTPS requirement. // During development, you can disable the HTTPS requirement.

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

@ -210,6 +210,8 @@ namespace OpenIddict.Infrastructure {
await services.Tokens.RevokeAsync(token); await services.Tokens.RevokeAsync(token);
context.Validate(context.Ticket); context.Validate(context.Ticket);
return;
} }
// Note: the OpenID Connect server middleware automatically reuses the authentication ticket // Note: the OpenID Connect server middleware automatically reuses the authentication ticket
@ -271,6 +273,8 @@ namespace OpenIddict.Infrastructure {
context.Options.AuthenticationScheme); context.Options.AuthenticationScheme);
context.Validate(ticket); context.Validate(ticket);
return;
} }
else if (context.Request.IsPasswordGrantType()) { else if (context.Request.IsPasswordGrantType()) {
@ -285,80 +289,31 @@ namespace OpenIddict.Infrastructure {
"given username was not found in the database: {Username}.", context.Request.Username); "given username was not found in the database: {Username}.", context.Request.Username);
} }
else { // Return an error if the username corresponds to the registered
// Ensure the user is allowed to sign in. // email address and if the "email" scope has not been requested.
if (!await services.SignIn.CanSignInAsync(user)) { else if (services.Users.SupportsUserEmail && context.Request.HasScope(OpenIdConnectConstants.Scopes.Profile) &&
services.Logger.LogError("The token request was rejected because the user '{Username}' " + !context.Request.HasScope(OpenIdConnectConstants.Scopes.Email)) {
"was not allowed to sign in.", context.Request.Username); // Retrieve the username and the email address associated with the user.
var username = await services.Users.GetUserNameAsync(user);
context.Reject( var email = await services.Users.GetEmailAsync(user);
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "The user is not allowed to sign in.");
return;
}
// 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);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "Account locked out.");
return;
}
// Reject the token request if two-factor authentication has been enabled by the user. if (!string.IsNullOrEmpty(email) && string.Equals(username, email, StringComparison.OrdinalIgnoreCase)) {
if (services.Users.SupportsUserTwoFactor && await services.Users.GetTwoFactorEnabledAsync(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 two-factor authentication " + "to prevent data leakage, the 'email' scope must be granted when the username " +
"was required for the account '{Username}.", context.Request.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: "Two-factor authentication is required for this account."); description: "The 'email' scope is required.");
return; return;
} }
// Return an error if the username corresponds to the registered
// 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);
if (!string.IsNullOrEmpty(email) && string.Equals(username, email, StringComparison.OrdinalIgnoreCase)) {
services.Logger.LogError("The token request was rejected because the 'email' scope was not requested: " +
"to prevent data leakage, the 'email' scope must be granted when the username " +
"is identical to the email address associated with the user profile.");
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "The 'email' scope is required.");
return;
}
}
} }
context.SkipToNextMiddleware();
} }
else if (context.Request.IsClientCredentialsGrantType()) { // Invoke the rest of the pipeline to allow
// Note: at this stage, the client credentials cannot be null or invalid, as client authentication is required // the user code to handle the token request.
// to use the client credentials grant and is automatically enforced by the OpenID Connect server middleware. context.SkipToNextMiddleware();
Debug.Assert(!string.IsNullOrEmpty(context.Request.ClientId) &&
!string.IsNullOrEmpty(context.Request.ClientSecret), "The client credentials shouldn't be null.");
context.SkipToNextMiddleware();
}
else {
context.SkipToNextMiddleware();
}
} }
} }
} }
Loading…
Cancel
Save