Skip to content
This repository has been archived by the owner on Jul 31, 2024. It is now read-only.

Commit

Permalink
Add API to interaction service to return error to client #2766
Browse files Browse the repository at this point in the history
  • Loading branch information
brockallen committed Mar 24, 2020
1 parent 8f0b87a commit ee60e9d
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public async Task<IActionResult> Login(LoginInputModel model, string button)
// if the user cancels, send a result back into IdentityServer as if they
// denied the consent (even if this client does not require consent).
// this will send back an access denied OIDC error response to the client.
await _interaction.GrantConsentAsync(context, ConsentResponse.Denied);
await _interaction.DenyAuthorizationAsync(context, AuthorizationError.AccessDenied);

// we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
if (context.IsNativeClient())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ private async Task<ProcessConsentResult> ProcessConsent(ConsentInputModel model)
// user clicked 'no' - send back the standard 'access_denied' response
if (model?.Button == "no")
{
grantedConsent = ConsentResponse.Denied;
grantedConsent = new ConsentResponse { Error = AuthorizationError.AccessDenied };

// emit event
await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.ScopeValues));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ private async Task<ProcessConsentResult> ProcessConsent(DeviceAuthorizationInput
// user clicked 'no' - send back the standard 'access_denied' response
if (model.Button == "no")
{
grantedConsent = ConsentResponse.Denied;
grantedConsent = new ConsentResponse { Error = AuthorizationError.AccessDenied };

// emit event
await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.ScopeValues));
Expand Down
8 changes: 4 additions & 4 deletions src/IdentityServer4/src/Endpoints/Results/AuthorizeResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,14 @@ private async Task ProcessErrorAsync(HttpContext context)
{
// these are the conditions where we can send a response
// back directly to the client, otherwise we're only showing the error UI
var isPromptNoneError = Response.Error == OidcConstants.AuthorizeErrors.AccountSelectionRequired ||
var isSafeError =
Response.Error == OidcConstants.AuthorizeErrors.AccessDenied ||
Response.Error == OidcConstants.AuthorizeErrors.AccountSelectionRequired ||
Response.Error == OidcConstants.AuthorizeErrors.LoginRequired ||
Response.Error == OidcConstants.AuthorizeErrors.ConsentRequired ||
Response.Error == OidcConstants.AuthorizeErrors.InteractionRequired;

if (Response.Error == OidcConstants.AuthorizeErrors.AccessDenied ||
(isPromptNoneError && Response.Request?.PromptMode == OidcConstants.PromptModes.None)
)
if (isSafeError)
{
// this scenario we can return back to the client
await ProcessResponseAsync(context);
Expand Down
43 changes: 40 additions & 3 deletions src/IdentityServer4/src/Models/Messages/ConsentResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,22 @@ namespace IdentityServer4.Models
public class ConsentResponse
{
/// <summary>
/// A denied consent response
/// Error, if any, for the consent response.
/// </summary>
public static ConsentResponse Denied = new ConsentResponse();
public AuthorizationError? Error { get; set; }

/// <summary>
/// Error description.
/// </summary>
public string ErrorDescription { get; set; }

/// <summary>
/// Gets if consent was granted.
/// </summary>
/// <value>
/// <c>true</c> if consent was granted; otherwise, <c>false</c>.
/// </value>
public bool Granted => ScopesValuesConsented != null && ScopesValuesConsented.Any();
public bool Granted => ScopesValuesConsented != null && ScopesValuesConsented.Any() && Error == null;

/// <summary>
/// Gets or sets the scope values consented to.
Expand All @@ -41,4 +46,36 @@ public class ConsentResponse
/// </value>
public bool RememberConsent { get; set; }
}

/// <summary>
/// Enum to model interaction authorization errors.
/// </summary>
public enum AuthorizationError
{
/// <summary>
/// Access denied
/// </summary>
AccessDenied,

/// <summary>
/// Interaction required
/// </summary>
InteractionRequired,

/// <summary>
/// Login required
/// </summary>
LoginRequired,

/// <summary>
/// Account selection required
/// </summary>
AccountSelectionRequired,

/// <summary>
/// Consent required
/// </summary>
ConsentRequired,
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,21 @@ public virtual async Task<InteractionResponse> ProcessInteractionAsync(Validated
{
Logger.LogTrace("ProcessInteractionAsync");

if (consent != null && consent.Granted == false && request.Subject.IsAuthenticated() == false)
if (consent != null && consent.Granted == false && consent.Error.HasValue && request.Subject.IsAuthenticated() == false)
{
// special case when anonymous user has issued a deny prior to authenticating
Logger.LogInformation("Error: User denied consent");
// special case when anonymous user has issued an error prior to authenticating
Logger.LogInformation("Error: User consent result: {error}", consent.Error);

var error = consent.Error == AuthorizationError.AccountSelectionRequired ? OidcConstants.AuthorizeErrors.AccountSelectionRequired :
consent.Error == AuthorizationError.ConsentRequired ? OidcConstants.AuthorizeErrors.ConsentRequired :
consent.Error == AuthorizationError.InteractionRequired ? OidcConstants.AuthorizeErrors.InteractionRequired :
consent.Error == AuthorizationError.LoginRequired ? OidcConstants.AuthorizeErrors.LoginRequired :
OidcConstants.AuthorizeErrors.AccessDenied;

return new InteractionResponse
{
Error = OidcConstants.AuthorizeErrors.AccessDenied
Error = error,
ErrorDescription = consent.ErrorDescription
};
}

Expand Down Expand Up @@ -269,9 +277,17 @@ protected internal virtual async Task<InteractionResponse> ProcessConsentAsync(V
if (consent.Granted == false)
{
// no need to show consent screen again
// build access denied error to return to client
response.Error = OidcConstants.AuthorizeErrors.AccessDenied;
Logger.LogInformation("Error: User denied consent");
// build error to return to client
Logger.LogInformation("Error: User consent result: {error}", consent.Error);

var error = consent.Error == AuthorizationError.AccountSelectionRequired ? OidcConstants.AuthorizeErrors.AccountSelectionRequired :
consent.Error == AuthorizationError.ConsentRequired ? OidcConstants.AuthorizeErrors.ConsentRequired :
consent.Error == AuthorizationError.InteractionRequired ? OidcConstants.AuthorizeErrors.InteractionRequired :
consent.Error == AuthorizationError.LoginRequired ? OidcConstants.AuthorizeErrors.LoginRequired :
OidcConstants.AuthorizeErrors.AccessDenied;

response.Error = error;
response.ErrorDescription = consent.ErrorDescription;
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,16 @@ public async Task GrantConsentAsync(AuthorizationRequest request, ConsentRespons
await _consentMessageStore.WriteAsync(consentRequest.Id, new Message<ConsentResponse>(consent, _clock.UtcNow.UtcDateTime));
}

public Task DenyAuthorizationAsync(AuthorizationRequest request, AuthorizationError error, string errorDescription = null)
{
var response = new ConsentResponse
{
Error = error,
ErrorDescription = errorDescription
};
return GrantConsentAsync(request, response);
}

public bool IsValidReturnUrl(string returnUrl)
{
var result = _returnUrlParser.IsValidReturnUrl(returnUrl);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ public interface IIdentityServerInteractionService
/// <param name="subject">The subject.</param>
Task GrantConsentAsync(AuthorizationRequest request, ConsentResponse consent, string subject = null);

/// <summary>
/// Triggers error back to the client for the authorization request.
/// This API is a simpler helper on top of GrantConsentAsync.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="error"></param>
/// <param name="errorDescription"></param>
Task DenyAuthorizationAsync(AuthorizationRequest request, AuthorizationError error, string errorDescription = null);

/// <summary>
/// Returns a collection representing all of the user's consents and grants.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ public async Task GrantConsentAsync_should_allow_deny_for_anonymous_users()
Client = new Client { ClientId = "client" },
ValidatedResources = _resourceValidationResult
};
await _subject.GrantConsentAsync(req, ConsentResponse.Denied, null);
await _subject.GrantConsentAsync(req, new ConsentResponse { Error = AuthorizationError.AccessDenied }, null);
}

[Fact]
Expand Down

0 comments on commit ee60e9d

Please sign in to comment.