//-----------------------------------------------------------------------
//
// Copyright (c) Outercurve Foundation. All rights reserved.
//
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.OAuth2 {
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Linq;
#if CLR4
using System.Net.Http;
#endif
using System.Security.Cryptography;
using System.Text;
using System.Web;
using DotNetOpenAuth.Configuration;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OAuth2.ChannelElements;
using DotNetOpenAuth.OAuth2.Messages;
///
/// Authorization Server supporting the web server flow.
///
public class AuthorizationServer {
///
/// A reusable instance of the scope satisfied checker.
///
private static readonly IScopeSatisfiedCheck DefaultScopeSatisfiedCheck = new StandardScopeSatisfiedCheck();
///
/// The list of modules that verify client authentication data.
///
private readonly List clientAuthenticationModules = new List();
///
/// The lone aggregate client authentication module that uses the and applies aggregating policy.
///
private readonly ClientAuthenticationModule aggregatingClientAuthenticationModule;
///
/// Initializes a new instance of the class.
///
/// The authorization server.
public AuthorizationServer(IAuthorizationServerHost authorizationServer) {
Requires.NotNull(authorizationServer, "authorizationServer");
this.aggregatingClientAuthenticationModule = new AggregatingClientCredentialReader(this.clientAuthenticationModules);
this.Channel = new OAuth2AuthorizationServerChannel(authorizationServer, this.aggregatingClientAuthenticationModule);
this.clientAuthenticationModules.AddRange(OAuth2AuthorizationServerSection.Configuration.ClientAuthenticationModules.CreateInstances(true));
this.ScopeSatisfiedCheck = DefaultScopeSatisfiedCheck;
}
///
/// Gets the channel.
///
/// The channel.
public Channel Channel { get; internal set; }
///
/// Gets the authorization server.
///
/// The authorization server.
public IAuthorizationServerHost AuthorizationServerServices {
get { return ((IOAuth2ChannelWithAuthorizationServer)this.Channel).AuthorizationServer; }
}
///
/// Gets the extension modules that can read client authentication data from incoming messages.
///
public IList ClientAuthenticationModules {
get { return this.clientAuthenticationModules; }
}
///
/// Gets or sets the service that checks whether a granted set of scopes satisfies a required set of scopes.
///
public IScopeSatisfiedCheck ScopeSatisfiedCheck {
get { return ((IOAuth2ChannelWithAuthorizationServer)this.Channel).ScopeSatisfiedCheck; }
set { ((IOAuth2ChannelWithAuthorizationServer)this.Channel).ScopeSatisfiedCheck = value; }
}
///
/// Reads in a client's request for the Authorization Server to obtain permission from
/// the user to authorize the Client's access of some protected resource(s).
///
/// The HTTP request to read from.
/// The incoming request, or null if no OAuth message was attached.
/// Thrown if an unexpected OAuth message is attached to the incoming request.
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "unauthorizedclient", Justification = "Protocol required.")]
public EndUserAuthorizationRequest ReadAuthorizationRequest(HttpRequestBase request = null) {
if (request == null) {
request = this.Channel.GetRequestFromContext();
}
EndUserAuthorizationRequest message;
if (this.Channel.TryReadFromRequest(request, out message)) {
if (message.ResponseType == EndUserAuthorizationResponseType.AuthorizationCode) {
// Clients with no secrets can only request implicit grant types.
var client = this.AuthorizationServerServices.GetClientOrThrow(message.ClientIdentifier);
ErrorUtilities.VerifyProtocol(client.HasNonEmptySecret, Protocol.EndUserAuthorizationRequestErrorCodes.UnauthorizedClient);
}
}
return message;
}
///
/// Approves an authorization request and sends an HTTP response to the user agent to redirect the user back to the Client.
///
/// The authorization request to approve.
/// The username of the account that approved the request (or whose data will be accessed by the client).
/// The scope of access the client should be granted. If null, all scopes in the original request will be granted.
/// The Client callback URL to use when formulating the redirect to send the user agent back to the Client.
public void ApproveAuthorizationRequest(EndUserAuthorizationRequest authorizationRequest, string userName, IEnumerable scopes = null, Uri callback = null) {
Requires.NotNull(authorizationRequest, "authorizationRequest");
var response = this.PrepareApproveAuthorizationRequest(authorizationRequest, userName, scopes, callback);
this.Channel.Respond(response);
}
///
/// Rejects an authorization request and sends an HTTP response to the user agent to redirect the user back to the Client.
///
/// The authorization request to disapprove.
/// The Client callback URL to use when formulating the redirect to send the user agent back to the Client.
public void RejectAuthorizationRequest(EndUserAuthorizationRequest authorizationRequest, Uri callback = null) {
Requires.NotNull(authorizationRequest, "authorizationRequest");
var response = this.PrepareRejectAuthorizationRequest(authorizationRequest, callback);
this.Channel.Respond(response);
}
#if CLR4
///
/// Handles an incoming request to the authorization server's token endpoint.
///
/// The HTTP request.
/// The HTTP response to send to the client.
public OutgoingWebResponse HandleTokenRequest(HttpRequestMessage request) {
return this.HandleTokenRequest(new HttpRequestInfo(request));
}
#endif
///
/// Handles an incoming request to the authorization server's token endpoint.
///
/// The HTTP request.
/// The HTTP response to send to the client.
public OutgoingWebResponse HandleTokenRequest(HttpRequestBase request = null) {
if (request == null) {
request = this.Channel.GetRequestFromContext();
}
AccessTokenRequestBase requestMessage;
IProtocolMessage responseMessage;
try {
if (this.Channel.TryReadFromRequest(request, out requestMessage)) {
var accessTokenResult = this.AuthorizationServerServices.CreateAccessToken(requestMessage);
ErrorUtilities.VerifyHost(accessTokenResult != null, "IAuthorizationServerHost.CreateAccessToken must not return null.");
IAccessTokenRequestInternal accessRequestInternal = requestMessage;
accessRequestInternal.AccessTokenResult = accessTokenResult;
var successResponseMessage = this.PrepareAccessTokenResponse(requestMessage, accessTokenResult.AllowRefreshToken);
successResponseMessage.Lifetime = accessTokenResult.AccessToken.Lifetime;
var authCarryingRequest = requestMessage as IAuthorizationCarryingRequest;
if (authCarryingRequest != null) {
accessTokenResult.AccessToken.ApplyAuthorization(authCarryingRequest.AuthorizationDescription);
IAccessTokenIssuingResponse accessTokenIssuingResponse = successResponseMessage;
accessTokenIssuingResponse.AuthorizationDescription = accessTokenResult.AccessToken;
}
responseMessage = successResponseMessage;
} else {
responseMessage = new AccessTokenFailedResponse() { Error = Protocol.AccessTokenRequestErrorCodes.InvalidRequest };
}
} catch (TokenEndpointProtocolException ex) {
responseMessage = ex.GetResponse();
} catch (ProtocolException) {
responseMessage = new AccessTokenFailedResponse() { Error = Protocol.AccessTokenRequestErrorCodes.InvalidRequest };
}
return this.Channel.PrepareResponse(responseMessage);
}
///
/// Prepares a response to inform the Client that the user has rejected the Client's authorization request.
///
/// The authorization request.
/// The Client callback URL to use when formulating the redirect to send the user agent back to the Client.
/// The authorization response message to send to the Client.
public EndUserAuthorizationFailedResponse PrepareRejectAuthorizationRequest(EndUserAuthorizationRequest authorizationRequest, Uri callback = null) {
Requires.NotNull(authorizationRequest, "authorizationRequest");
Contract.Ensures(Contract.Result() != null);
if (callback == null) {
callback = this.GetCallback(authorizationRequest);
}
var response = new EndUserAuthorizationFailedResponse(callback, authorizationRequest);
return response;
}
///
/// Approves an authorization request.
///
/// The authorization request to approve.
/// The username of the account that approved the request (or whose data will be accessed by the client).
/// The scope of access the client should be granted. If null, all scopes in the original request will be granted.
/// The Client callback URL to use when formulating the redirect to send the user agent back to the Client.
/// The authorization response message to send to the Client.
public EndUserAuthorizationSuccessResponseBase PrepareApproveAuthorizationRequest(EndUserAuthorizationRequest authorizationRequest, string userName, IEnumerable scopes = null, Uri callback = null) {
Requires.NotNull(authorizationRequest, "authorizationRequest");
Requires.NotNullOrEmpty(userName, "userName");
Contract.Ensures(Contract.Result() != null);
if (callback == null) {
callback = this.GetCallback(authorizationRequest);
}
var client = this.AuthorizationServerServices.GetClientOrThrow(authorizationRequest.ClientIdentifier);
EndUserAuthorizationSuccessResponseBase response;
switch (authorizationRequest.ResponseType) {
case EndUserAuthorizationResponseType.AccessToken:
IAccessTokenRequestInternal accessRequestInternal = (EndUserAuthorizationImplicitRequest)authorizationRequest;
var accessTokenResult = this.AuthorizationServerServices.CreateAccessToken(accessRequestInternal);
ErrorUtilities.VerifyHost(accessTokenResult != null, "IAuthorizationServerHost.CreateAccessToken must not return null.");
accessRequestInternal.AccessTokenResult = accessTokenResult;
var implicitGrantResponse = new EndUserAuthorizationSuccessAccessTokenResponse(callback, authorizationRequest);
implicitGrantResponse.Lifetime = accessTokenResult.AccessToken.Lifetime;
accessTokenResult.AccessToken.ApplyAuthorization(implicitGrantResponse.Scope, userName, implicitGrantResponse.Lifetime);
IAccessTokenCarryingRequest tokenCarryingResponse = implicitGrantResponse;
tokenCarryingResponse.AuthorizationDescription = accessTokenResult.AccessToken;
response = implicitGrantResponse;
break;
case EndUserAuthorizationResponseType.AuthorizationCode:
var authCodeResponse = new EndUserAuthorizationSuccessAuthCodeResponseAS(callback, authorizationRequest);
IAuthorizationCodeCarryingRequest codeCarryingResponse = authCodeResponse;
codeCarryingResponse.AuthorizationDescription = new AuthorizationCode(
authorizationRequest.ClientIdentifier,
authorizationRequest.Callback,
authCodeResponse.Scope,
userName);
response = authCodeResponse;
break;
default:
throw ErrorUtilities.ThrowInternal("Unexpected response type.");
}
response.AuthorizingUsername = userName;
// Customize the approved scope if the authorization server has decided to do so.
if (scopes != null) {
response.Scope.ResetContents(scopes);
}
return response;
}
///
/// Decodes a refresh token into its authorization details.
///
/// The encoded refresh token as it would appear to the client.
/// A description of the authorization represented by the refresh token.
/// Thrown if the refresh token is not valid due to expiration, corruption or not being authentic.
///
/// This can be useful if the authorization server supports the client revoking its own access (on uninstall, for example).
/// Outside the scope of the OAuth 2 spec, the client may contact the authorization server host requesting that its refresh
/// token be revoked. The authorization server would need to decode the refresh token so it knows which authorization in
/// the database to delete.
///
public IAuthorizationDescription DecodeRefreshToken(string refreshToken) {
var refreshTokenFormatter = RefreshToken.CreateFormatter(this.AuthorizationServerServices.CryptoKeyStore);
var token = new RefreshToken();
refreshTokenFormatter.Deserialize(token, refreshToken);
return token;
}
///
/// Gets the redirect URL to use for a particular authorization request.
///
/// The authorization request.
/// The URL to redirect to. Never null.
/// Thrown if no callback URL could be determined.
protected Uri GetCallback(EndUserAuthorizationRequest authorizationRequest) {
Requires.NotNull(authorizationRequest, "authorizationRequest");
Contract.Ensures(Contract.Result() != null);
var client = this.AuthorizationServerServices.GetClientOrThrow(authorizationRequest.ClientIdentifier);
// Prefer a request-specific callback to the pre-registered one (if any).
if (authorizationRequest.Callback != null) {
// The OAuth channel has already validated the callback parameter against
// the authorization server's whitelist for this client.
return authorizationRequest.Callback;
}
// Since the request didn't include a callback URL, look up the callback from
// the client's preregistration with this authorization server.
Uri defaultCallback = client.DefaultCallback;
ErrorUtilities.VerifyProtocol(defaultCallback != null, AuthServerStrings.NoCallback);
return defaultCallback;
}
///
/// Prepares the response to an access token request.
///
/// The request for an access token.
/// If set to true, the response will include a long-lived refresh token.
/// The response message to send to the client.
private AccessTokenSuccessResponse PrepareAccessTokenResponse(AccessTokenRequestBase request, bool allowRefreshToken = true) {
Requires.NotNull(request, "request");
if (allowRefreshToken) {
if (request is AccessTokenClientCredentialsRequest) {
// Per OAuth 2.0 section 4.4.3 (draft 23), refresh tokens should never be included
// in a response to an access token request that used the client credential grant type.
Logger.OAuth.Debug("Suppressing refresh token in access token response because the grant type used by the client disallows it.");
allowRefreshToken = false;
}
}
var tokenRequest = (IAuthorizationCarryingRequest)request;
var accessTokenRequest = (IAccessTokenRequestInternal)request;
var response = new AccessTokenSuccessResponse(request) {
HasRefreshToken = allowRefreshToken,
};
response.Scope.ResetContents(tokenRequest.AuthorizationDescription.Scope);
return response;
}
}
}