//----------------------------------------------------------------------- // // Copyright (c) Andrew Arnott. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOAuth { using System; using System.Collections.Generic; using System.Globalization; using System.ServiceModel.Channels; using System.Web; using DotNetOAuth.ChannelElements; using DotNetOAuth.Messages; using DotNetOAuth.Messaging; using DotNetOAuth.Messaging.Bindings; /// /// A web application that allows access via OAuth. /// /// /// The Service Provider’s documentation should include: /// /// The URLs (Request URLs) the Consumer will use when making OAuth requests, and the HTTP methods (i.e. GET, POST, etc.) used in the Request Token URL and Access Token URL. /// Signature methods supported by the Service Provider. /// Any additional request parameters that the Service Provider requires in order to obtain a Token. Service Provider specific parameters MUST NOT begin with oauth_. /// /// public class ServiceProvider { /// /// Initializes a new instance of the class. /// /// The endpoints and behavior on the Service Provider. /// The host's method of storing and recalling tokens and secrets. public ServiceProvider(ServiceProviderDescription serviceDescription, ITokenManager tokenManager) : this(serviceDescription, tokenManager, new OAuthServiceProviderMessageTypeProvider(tokenManager)) { } /// /// Initializes a new instance of the class. /// /// The endpoints and behavior on the Service Provider. /// The host's method of storing and recalling tokens and secrets. /// An object that can figure out what type of message is being received for deserialization. public ServiceProvider(ServiceProviderDescription serviceDescription, ITokenManager tokenManager, OAuthServiceProviderMessageTypeProvider messageTypeProvider) { if (serviceDescription == null) { throw new ArgumentNullException("serviceDescription"); } if (tokenManager == null) { throw new ArgumentNullException("tokenManager"); } if (messageTypeProvider == null) { throw new ArgumentNullException("messageTypeProvider"); } var signingElement = serviceDescription.CreateTamperProtectionElement(); signingElement.SignatureVerificationCallback = this.TokenSignatureVerificationCallback; INonceStore store = new NonceMemoryStore(StandardExpirationBindingElement.DefaultMaximumMessageAge); this.ServiceDescription = serviceDescription; this.Channel = new OAuthChannel(signingElement, store, messageTypeProvider, new StandardWebRequestHandler()); this.TokenGenerator = new StandardTokenGenerator(); this.TokenManager = tokenManager; } /// /// Gets the description of this Service Provider. /// public ServiceProviderDescription ServiceDescription { get; private set; } /// /// Gets or sets the generator responsible for generating new tokens and secrets. /// public ITokenGenerator TokenGenerator { get; set; } /// /// Gets the persistence store for tokens and secrets. /// public ITokenManager TokenManager { get; private set; } /// /// Gets or sets the channel to use for sending/receiving messages. /// internal OAuthChannel Channel { get; set; } /// /// Reads any incoming OAuth message. /// /// The deserialized message. public IProtocolMessage ReadRequest() { return this.Channel.ReadFromRequest(); } /// /// Reads any incoming OAuth message. /// /// The HTTP request to read the message from. /// The deserialized message. public IProtocolMessage ReadRequest(HttpRequest request) { return this.Channel.ReadFromRequest(new HttpRequestInfo(request)); } /// /// Gets the incoming request for an unauthorized token, if any. /// /// The incoming request, or null if no OAuth message was attached. /// Thrown if an unexpected OAuth message is attached to the incoming request. /// /// Requires HttpContext.Current. /// public RequestTokenMessage ReadTokenRequest() { return this.ReadTokenRequest(this.Channel.GetRequestFromContext()); } /// /// Gets the incoming request for an unauthorized token, if any. /// /// The incoming HTTP request. /// The incoming request, or null if no OAuth message was attached. /// Thrown if an unexpected OAuth message is attached to the incoming request. public RequestTokenMessage ReadTokenRequest(HttpRequest request) { return this.ReadTokenRequest(new HttpRequestInfo(request)); } /// /// Sends an unauthorized token back to the Consumer for use in a user agent redirect /// for subsequent authorization. /// /// The token request message the Consumer sent that the Service Provider is now responding to. /// Any extra parameters the Consumer should receive with the OAuth message. May be null. /// The actual response the Service Provider will need to forward as the HTTP response. public Response SendUnauthorizedTokenResponse(RequestTokenMessage request, IDictionary extraParameters) { string token = this.TokenGenerator.GenerateRequestToken(request.ConsumerKey); string secret = this.TokenGenerator.GenerateSecret(); UnauthorizedRequestTokenMessage response = new UnauthorizedRequestTokenMessage { RequestToken = token, TokenSecret = secret, }; response.AddNonOAuthParameters(extraParameters); this.TokenManager.StoreNewRequestToken(request, response); return this.Channel.Send(response); } /// /// Gets the incoming request for the Service Provider to authorize a Consumer's /// access to some protected resources. /// /// The incoming request, or null if no OAuth message was attached. /// Thrown if an unexpected OAuth message is attached to the incoming request. /// /// Requires HttpContext.Current. /// public DirectUserToServiceProviderMessage ReadAuthorizationRequest() { return this.ReadAuthorizationRequest(this.Channel.GetRequestFromContext()); } /// /// Gets the incoming request for the Service Provider to authorize a Consumer's /// access to some protected resources. /// /// The incoming HTTP request. /// The incoming request, or null if no OAuth message was attached. /// Thrown if an unexpected OAuth message is attached to the incoming request. public DirectUserToServiceProviderMessage ReadAuthorizationRequest(HttpRequest request) { return this.ReadAuthorizationRequest(new HttpRequestInfo(request)); } /// /// Completes user authorization of a token by redirecting the user agent back to the Consumer. /// /// The Consumer's original authorization request. /// /// The pending user agent redirect based message to be sent as an HttpResponse, /// or null if the Consumer requested no callback. /// public Response SendAuthorizationResponse(DirectUserToServiceProviderMessage request) { if (request == null) { throw new ArgumentNullException("request"); } if (request.Callback != null) { var authorization = new DirectUserToConsumerMessage(request.Callback) { RequestToken = request.RequestToken, }; return this.Channel.Send(authorization); } else { return null; } } /// /// Gets the incoming request to exchange an authorized token for an access token. /// /// The incoming request, or null if no OAuth message was attached. /// Thrown if an unexpected OAuth message is attached to the incoming request. /// /// Requires HttpContext.Current. /// public RequestAccessTokenMessage ReadAccessTokenRequest() { return this.ReadAccessTokenRequest(this.Channel.GetRequestFromContext()); } /// /// Gets the incoming request to exchange an authorized token for an access token. /// /// The incoming HTTP request. /// The incoming request, or null if no OAuth message was attached. /// Thrown if an unexpected OAuth message is attached to the incoming request. public RequestAccessTokenMessage ReadAccessTokenRequest(HttpRequest request) { return this.ReadAccessTokenRequest(new HttpRequestInfo(request)); } /// /// Prepares and sends an access token to a Consumer, and invalidates the request token. /// /// The Consumer's message requesting an access token. /// Any extra parameters the Consumer should receive with the OAuth message. May be null. /// The HTTP response to actually send to the Consumer. public Response SendAccessToken(RequestAccessTokenMessage request, IDictionary extraParameters) { if (request == null) { throw new ArgumentNullException("request"); } if (!this.TokenManager.IsRequestTokenAuthorized(request.RequestToken)) { throw new ProtocolException( string.Format( CultureInfo.CurrentCulture, Strings.AccessTokenNotAuthorized, request.RequestToken)); } string accessToken = this.TokenGenerator.GenerateAccessToken(request.ConsumerKey); string tokenSecret = this.TokenGenerator.GenerateSecret(); this.TokenManager.ExpireRequestTokenAndStoreNewAccessToken(request.ConsumerKey, request.RequestToken, accessToken, tokenSecret); var grantAccess = new GrantAccessTokenMessage { AccessToken = accessToken, TokenSecret = tokenSecret, }; grantAccess.AddNonOAuthParameters(extraParameters); return this.Channel.Send(grantAccess); } /// /// Gets the authorization (access token) for accessing some protected resource. /// /// The authorization message sent by the Consumer, or null if no authorization message is attached. /// /// This method verifies that the access token and token secret are valid. /// It falls on the caller to verify that the access token is actually authorized /// to access the resources being requested. /// /// Thrown if an unexpected message is attached to the request. public AccessProtectedResourcesMessage GetProtectedResourceAuthorization() { return this.GetProtectedResourceAuthorization(this.Channel.GetRequestFromContext()); } /// /// Gets the authorization (access token) for accessing some protected resource. /// /// The incoming HTTP request. /// The authorization message sent by the Consumer, or null if no authorization message is attached. /// /// This method verifies that the access token and token secret are valid. /// It falls on the caller to verify that the access token is actually authorized /// to access the resources being requested. /// /// Thrown if an unexpected message is attached to the request. public AccessProtectedResourcesMessage GetProtectedResourceAuthorization(HttpRequest request) { return this.GetProtectedResourceAuthorization(new HttpRequestInfo(request)); } /// /// Gets the authorization (access token) for accessing some protected resource. /// /// HTTP details from an incoming WCF message. /// The URI of the WCF service endpoint. /// The authorization message sent by the Consumer, or null if no authorization message is attached. /// /// This method verifies that the access token and token secret are valid. /// It falls on the caller to verify that the access token is actually authorized /// to access the resources being requested. /// /// Thrown if an unexpected message is attached to the request. public AccessProtectedResourcesMessage GetProtectedResourceAuthorization(HttpRequestMessageProperty request, Uri requestUri) { return this.GetProtectedResourceAuthorization(new HttpRequestInfo(request, requestUri)); } /// /// Reads a request for an unauthorized token from the incoming HTTP request. /// /// 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. internal RequestTokenMessage ReadTokenRequest(HttpRequestInfo request) { RequestTokenMessage message; this.Channel.TryReadFromRequest(request, out message); return message; } /// /// Reads in a Consumer's request for the Service Provider to obtain permission from /// the user to authorize the Consumer'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. internal DirectUserToServiceProviderMessage ReadAuthorizationRequest(HttpRequestInfo request) { DirectUserToServiceProviderMessage message; this.Channel.TryReadFromRequest(request, out message); return message; } /// /// Reads in a Consumer's request to exchange an authorized request token for an access token. /// /// 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. internal RequestAccessTokenMessage ReadAccessTokenRequest(HttpRequestInfo request) { RequestAccessTokenMessage message; this.Channel.TryReadFromRequest(request, out message); return message; } /// /// Gets the authorization (access token) for accessing some protected resource. /// /// The incoming HTTP request. /// The authorization message sent by the Consumer, or null if no authorization message is attached. /// /// This method verifies that the access token and token secret are valid. /// It falls on the caller to verify that the access token is actually authorized /// to access the resources being requested. /// /// Thrown if an unexpected message is attached to the request. internal AccessProtectedResourcesMessage GetProtectedResourceAuthorization(HttpRequestInfo request) { if (request == null) { throw new ArgumentNullException("request"); } AccessProtectedResourcesMessage accessMessage; if (this.Channel.TryReadFromRequest(request, out accessMessage)) { if (this.TokenManager.GetTokenType(accessMessage.AccessToken) != TokenType.AccessToken) { throw new ProtocolException( string.Format( CultureInfo.CurrentCulture, Strings.BadAccessTokenInProtectedResourceRequest, accessMessage.AccessToken)); } } return accessMessage; } /// /// Fills out the secrets in an incoming message so that signature verification can be performed. /// /// The incoming message. private void TokenSignatureVerificationCallback(ITamperResistantOAuthMessage message) { message.ConsumerSecret = this.TokenManager.GetConsumerSecret(message.ConsumerKey); var tokenMessage = message as ITokenContainingMessage; if (tokenMessage != null) { message.TokenSecret = this.TokenManager.GetTokenSecret(tokenMessage.Token); } } } }