//----------------------------------------------------------------------- // // 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; } } }