//----------------------------------------------------------------------- // // Copyright (c) Outercurve Foundation. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOpenAuth.OAuth2 { using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Net.Http; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Web; using DotNetOpenAuth.Configuration; using DotNetOpenAuth.Logging; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth2.ChannelElements; using DotNetOpenAuth.OAuth2.Messages; using Validation; /// /// 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, null)); 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 cancellation token. /// /// 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 async Task ReadAuthorizationRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNull(request, "request"); var message = await this.Channel.TryReadFromRequestAsync(request, cancellationToken); if (message != null) { 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; } /// /// 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 cancellation token. /// /// The incoming request, or null if no OAuth message was attached. /// /// Thrown if an unexpected OAuth message is attached to the incoming request. public Task ReadAuthorizationRequestAsync( HttpRequestBase request = null, CancellationToken cancellationToken = default(CancellationToken)) { request = request ?? this.Channel.GetRequestFromContext(); return this.ReadAuthorizationRequestAsync(request.AsHttpRequestMessage(), cancellationToken); } /// /// 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 URL that carries the authorization request. /// The cancellation token. /// /// The incoming request, or null if no OAuth message was attached. /// /// Thrown if an unexpected OAuth message is attached to the incoming request. public Task ReadAuthorizationRequestAsync(Uri requestUri, CancellationToken cancellationToken = default(CancellationToken)) { var request = new HttpRequestMessage(HttpMethod.Get, requestUri); return this.ReadAuthorizationRequestAsync(request, cancellationToken); } /// /// Handles an incoming request to the authorization server's token endpoint. /// /// The HTTP request. /// The cancellation token. /// /// The HTTP response to send to the client. /// public async Task HandleTokenRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNull(request, "request"); IProtocolMessage responseMessage; try { AccessTokenRequestBase requestMessage = await this.Channel.TryReadFromRequestAsync(request, cancellationToken); if (requestMessage != null) { 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 await this.Channel.PrepareResponseAsync(responseMessage, cancellationToken); } /// /// Handles an incoming request to the authorization server's token endpoint. /// /// The HTTP request. /// The cancellation token. /// /// The HTTP response to send to the client. /// public Task HandleTokenRequestAsync(HttpRequestBase request = null, CancellationToken cancellationToken = default(CancellationToken)) { request = request ?? this.Channel.GetRequestFromContext(); return this.HandleTokenRequestAsync(request.AsHttpRequestMessage(), cancellationToken); } /// /// 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"); 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"); 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"); 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; } } }