//-----------------------------------------------------------------------
//
// 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;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OAuth2.ChannelElements;
using DotNetOpenAuth.OAuth2.Messages;
///
/// Authorization Server supporting the web server flow.
///
public class AuthorizationServer {
///
/// Initializes a new instance of the class.
///
/// The authorization server.
public AuthorizationServer(IAuthorizationServer authorizationServer) {
Requires.NotNull(authorizationServer, "authorizationServer");
this.Channel = new OAuth2AuthorizationServerChannel(authorizationServer);
}
///
/// Gets the channel.
///
/// The channel.
public Channel Channel { get; internal set; }
///
/// Gets the authorization server.
///
/// The authorization server.
public IAuthorizationServer AuthorizationServerServices {
get { return ((IOAuth2ChannelWithAuthorizationServer)this.Channel).AuthorizationServer; }
}
///
/// 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(!String.IsNullOrEmpty(client.Secret), Protocol.unauthorized_client);
}
}
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);
}
///
/// Checks the incoming HTTP request for an access token request and prepares a response if the request message was found.
///
/// The formulated response, or null if the request was not found..
/// A value indicating whether any access token request was found in the HTTP request.
///
/// This method assumes that the authorization server and the resource server are the same and that they share a single
/// asymmetric key for signing and encrypting the access token. If this is not true, use the method instead.
///
public bool TryPrepareAccessTokenResponse(out IDirectResponseProtocolMessage response) {
return this.TryPrepareAccessTokenResponse(this.Channel.GetRequestFromContext(), out response);
}
///
/// Checks the incoming HTTP request for an access token request and prepares a response if the request message was found.
///
/// The HTTP request info.
/// The formulated response, or null if the request was not found..
/// A value indicating whether any access token request was found in the HTTP request.
///
/// This method assumes that the authorization server and the resource server are the same and that they share a single
/// asymmetric key for signing and encrypting the access token. If this is not true, use the method instead.
///
public bool TryPrepareAccessTokenResponse(HttpRequestBase httpRequestInfo, out IDirectResponseProtocolMessage response) {
Requires.NotNull(httpRequestInfo, "httpRequestInfo");
Contract.Ensures(Contract.Result() == (Contract.ValueAtReturn(out response) != null));
var request = this.ReadAccessTokenRequest(httpRequestInfo);
if (request != null) {
response = this.PrepareAccessTokenResponse(request);
return true;
}
response = null;
return false;
}
///
/// Reads the access token request.
///
/// The request info.
/// The Client's request for an access token; or null if no such message was found in the request.
public AccessTokenRequestBase ReadAccessTokenRequest(HttpRequestBase requestInfo = null) {
if (requestInfo == null) {
requestInfo = this.Channel.GetRequestFromContext();
}
AccessTokenRequestBase request;
this.Channel.TryReadFromRequest(requestInfo, out request);
return request;
}
///
/// 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:
var accessTokenResponse = new EndUserAuthorizationSuccessAccessTokenResponse(callback, authorizationRequest);
accessTokenResponse.Lifetime = this.AuthorizationServerServices.GetAccessTokenLifetime((EndUserAuthorizationImplicitRequest)authorizationRequest);
response = accessTokenResponse;
break;
case EndUserAuthorizationResponseType.AuthorizationCode:
response = new EndUserAuthorizationSuccessAuthCodeResponse(callback, authorizationRequest);
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;
}
///
/// 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.
public virtual IDirectResponseProtocolMessage PrepareAccessTokenResponse(AccessTokenRequestBase request, bool includeRefreshToken = true) {
Requires.NotNull(request, "request");
if (includeRefreshToken) {
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.");
includeRefreshToken = false;
}
}
var tokenRequest = (IAuthorizationCarryingRequest)request;
var response = new AccessTokenSuccessResponse(request) {
Lifetime = this.AuthorizationServerServices.GetAccessTokenLifetime(request),
HasRefreshToken = includeRefreshToken,
};
response.Scope.ResetContents(tokenRequest.AuthorizationDescription.Scope);
return response;
}
///
/// 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, OAuthStrings.NoCallback);
return defaultCallback;
}
}
}