//----------------------------------------------------------------------- // // 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.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); } /// /// 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)) { IAccessTokenRequestInternal accessRequestInternal = requestMessage; accessRequestInternal.AccessTokenCreationParameters = this.AuthorizationServerServices.GetAccessTokenParameters(requestMessage); ErrorUtilities.VerifyHost(accessRequestInternal.AccessTokenCreationParameters != null, "IAuthorizationServerHost.GetAccessTokenParameters must not return null."); var successResponseMessage = this.PrepareAccessTokenResponse(requestMessage, accessRequestInternal.AccessTokenCreationParameters.IncludeRefreshToken); successResponseMessage.Lifetime = accessRequestInternal.AccessTokenCreationParameters.AccessTokenLifetime; var authCarryingRequest = requestMessage as IAuthorizationCarryingRequest; if (authCarryingRequest != null) { IAccessTokenIssuingResponse accessTokenIssuingResponse = successResponseMessage; accessTokenIssuingResponse.AuthorizationDescription = new AccessToken(authCarryingRequest.AuthorizationDescription, successResponseMessage.Lifetime); accessTokenIssuingResponse.AuthorizationDescription.ExtraData.AddRange(accessRequestInternal.AccessTokenCreationParameters.ExtraClaims); } 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; accessRequestInternal.AccessTokenCreationParameters = this.AuthorizationServerServices.GetAccessTokenParameters(accessRequestInternal); var implicitGrantResponse = new EndUserAuthorizationSuccessAccessTokenResponse(callback, authorizationRequest); implicitGrantResponse.Lifetime = accessRequestInternal.AccessTokenCreationParameters.AccessTokenLifetime; IAccessTokenCarryingRequest tokenCarryingResponse = implicitGrantResponse; tokenCarryingResponse.AuthorizationDescription = new AccessToken( implicitGrantResponse.Scope, userName, implicitGrantResponse.Lifetime); tokenCarryingResponse.AuthorizationDescription.ExtraData.AddRange(accessRequestInternal.AccessTokenCreationParameters.ExtraClaims); 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; } /// /// 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 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 accessTokenRequest = (IAccessTokenRequestInternal)request; var response = new AccessTokenSuccessResponse(request) { HasRefreshToken = includeRefreshToken, }; response.Scope.ResetContents(tokenRequest.AuthorizationDescription.Scope); return response; } } }