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.
if (!await _userManager.CheckPasswordAsync(user, request.Password)) {
if (_userManager.SupportsUserLockout) {
await _userManager.AccessFailedAsync(user);
}
return BadRequest(new OpenIdConnectResponse {
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The username/password couple is invalid."
});
}
if (_userManager.SupportsUserLockout) {
await _userManager.ResetAccessFailedCountAsync(user);
}
// Note: for a more complete sample including account lockout support, visit
// https://github.com/openiddict/openiddict-core/blob/dev/samples/Mvc.Server/Controllers/AuthorizationController.cs
var identity = await _userManager.CreateIdentityAsync(user, request.GetScopes());

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

@ -36,6 +36,9 @@ namespace Mvc.Server {
_userManager = userManager;
}
// Note: to support interactive flows like the code flow,
// you must provide your own authorization endpoint action:
[Authorize, HttpGet, Route("~/connect/authorize")]
public async Task<IActionResult> Authorize() {
// Extract the authorization request from the ASP.NET environment.
@ -121,56 +124,81 @@ namespace Mvc.Server {
return SignOut(OpenIdConnectServerDefaults.AuthenticationScheme);
}
// Note: to support the password grant type, you must provide your own token endpoint action:
// [HttpPost("~/connect/token")]
// [Produces("application/json")]
// public async Task<IActionResult> Exchange() {
// var request = HttpContext.GetOpenIdConnectRequest();
//
// if (request.IsPasswordGrantType()) {
// var user = await _userManager.FindByNameAsync(request.Username);
// if (user == null) {
// return BadRequest(new OpenIdConnectResponse {
// Error = OpenIdConnectConstants.Errors.InvalidGrant,
// ErrorDescription = "The username/password couple is invalid."
// });
// }
//
// // Ensure the password is valid.
// if (!await _userManager.CheckPasswordAsync(user, request.Password)) {
// if (_userManager.SupportsUserLockout) {
// await _userManager.AccessFailedAsync(user);
// }
//
// return BadRequest(new OpenIdConnectResponse {
// 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."
// });
// }
// Note: to support non-interactive flows like password,
// you must provide your own token endpoint action:
[HttpPost("~/connect/token")]
[Produces("application/json")]
public async Task<IActionResult> Exchange() {
var request = HttpContext.GetOpenIdConnectRequest();
if (request.IsPasswordGrantType()) {
var user = await _userManager.FindByNameAsync(request.Username);
if (user == null) {
return BadRequest(new OpenIdConnectResponse {
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The username/password couple is invalid."
});
}
// Ensure the user is allowed to sign in.
if (!await _signInManager.CanSignInAsync(user)) {
return BadRequest(new OpenIdConnectResponse {
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The specified user is not allowed to sign in."
});
}
// 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 {
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The specified user is not allowed to sign in."
});
}
// Ensure the user is not already locked out.
if (_userManager.SupportsUserLockout && await _userManager.IsLockedOutAsync(user)) {
return BadRequest(new OpenIdConnectResponse {
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The username/password couple is invalid."
});
}
// Ensure the password is valid.
if (!await _userManager.CheckPasswordAsync(user, request.Password)) {
if (_userManager.SupportsUserLockout) {
await _userManager.AccessFailedAsync(user);
}
return BadRequest(new OpenIdConnectResponse {
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")
.EnableUserinfoEndpoint("/connect/userinfo")
// Note: the Mvc.Client sample only uses the authorization code flow but you can enable
// the other flows if you need to support implicit, password or client credentials.
// Note: the Mvc.Client sample only uses the code flow and the password flow, but you
// can enable the other flows if you need to support implicit or client credentials.
.AllowAuthorizationCodeFlow()
.AllowPasswordFlow()
.AllowRefreshTokenFlow()
// 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);
context.Validate(context.Ticket);
return;
}
// Note: the OpenID Connect server middleware automatically reuses the authentication ticket
@ -271,6 +273,8 @@ namespace OpenIddict.Infrastructure {
context.Options.AuthenticationScheme);
context.Validate(ticket);
return;
}
else if (context.Request.IsPasswordGrantType()) {
@ -285,80 +289,31 @@ namespace OpenIddict.Infrastructure {
"given username was not found in the database: {Username}.", context.Request.Username);
}
else {
// Ensure the user is allowed to sign in.
if (!await services.SignIn.CanSignInAsync(user)) {
services.Logger.LogError("The token request was rejected because the user '{Username}' " +
"was not allowed to sign in.", context.Request.Username);
context.Reject(
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;
}
// Return an error if the username corresponds to the registered
// email address and if the "email" scope has not been requested.
else 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);
// 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);
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.InvalidGrant,
description: "Two-factor authentication is required for this account.");
error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "The 'email' scope is required.");
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()) {
// Note: at this stage, the client credentials cannot be null or invalid, as client authentication is required
// to use the client credentials grant and is automatically enforced by the OpenID Connect server middleware.
Debug.Assert(!string.IsNullOrEmpty(context.Request.ClientId) &&
!string.IsNullOrEmpty(context.Request.ClientSecret), "The client credentials shouldn't be null.");
context.SkipToNextMiddleware();
}
else {
context.SkipToNextMiddleware();
}
// Invoke the rest of the pipeline to allow
// the user code to handle the token request.
context.SkipToNextMiddleware();
}
}
}
Loading…
Cancel
Save