//----------------------------------------------------------------------- // // 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 { /// /// The field behind the property. /// private OAuthChannel channel; /// /// 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(); INonceStore store = new NonceMemoryStore(StandardExpirationBindingElement.DefaultMaximumMessageAge); this.ServiceDescription = serviceDescription; this.OAuthChannel = new OAuthChannel(signingElement, store, tokenManager, messageTypeProvider, new StandardWebRequestHandler()); this.TokenGenerator = new StandardTokenGenerator(); } /// /// 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 { return this.OAuthChannel.TokenManager; } } /// /// Gets the channel to use for sending/receiving messages. /// public Channel Channel { get { return this.OAuthChannel; } } /// /// Gets or sets the channel to use for sending/receiving messages. /// internal OAuthChannel OAuthChannel { get { return this.channel; } set { if (this.channel != null) { this.channel.Sending -= this.OAuthChannel_Sending; } this.channel = value; if (this.channel != null) { this.channel.Sending += this.OAuthChannel_Sending; } } } /// /// Reads any incoming OAuth message. /// /// The deserialized message. /// /// Requires HttpContext.Current. /// public IOAuthDirectedMessage ReadRequest() { return (IOAuthDirectedMessage)this.Channel.ReadFromRequest(); } /// /// Reads any incoming OAuth message. /// /// The HTTP request to read the message from. /// The deserialized message. public IOAuthDirectedMessage ReadRequest(HttpRequest request) { return (IOAuthDirectedMessage)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 UnauthorizedTokenRequest 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 UnauthorizedTokenRequest ReadTokenRequest(HttpRequest request) { return this.ReadTokenRequest(new HttpRequestInfo(request)); } /// /// 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. public UnauthorizedTokenRequest ReadTokenRequest(HttpRequestInfo request) { UnauthorizedTokenRequest message; this.Channel.TryReadFromRequest(request, out message); return message; } /// /// Prepares a message containing an unauthorized token for the Consumer to use in a /// user agent redirect for subsequent authorization. /// /// The token request message the Consumer sent that the Service Provider is now responding to. /// The response message to send using the , after optionally adding extra data to it. public UnauthorizedTokenResponse PrepareUnauthorizedTokenMessage(UnauthorizedTokenRequest request) { if (request == null) { throw new ArgumentNullException("request"); } string token = this.TokenGenerator.GenerateRequestToken(request.ConsumerKey); string secret = this.TokenGenerator.GenerateSecret(); UnauthorizedTokenResponse response = new UnauthorizedTokenResponse(request, token, secret); return 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 UserAuthorizationRequest 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 UserAuthorizationRequest ReadAuthorizationRequest(HttpRequest request) { return this.ReadAuthorizationRequest(new HttpRequestInfo(request)); } /// /// 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. public UserAuthorizationRequest ReadAuthorizationRequest(HttpRequestInfo request) { UserAuthorizationRequest message; this.Channel.TryReadFromRequest(request, out message); return message; } /// /// Prepares the message to send back to the consumer following proper authorization of /// a token by an interactive user at the Service Provider's web site. /// /// The Consumer's original authorization request. /// /// The message to send to the Consumer using if one is necessary. /// Null if the Consumer did not request a callback. /// public UserAuthorizationResponse PrepareAuthorizationResponse(UserAuthorizationRequest request) { if (request == null) { throw new ArgumentNullException("request"); } if (request.Callback != null) { var authorization = new UserAuthorizationResponse(request.Callback) { RequestToken = request.RequestToken, }; return 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 AuthorizedTokenRequest 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 AuthorizedTokenRequest ReadAccessTokenRequest(HttpRequest request) { return this.ReadAccessTokenRequest(new HttpRequestInfo(request)); } /// /// 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. public AuthorizedTokenRequest ReadAccessTokenRequest(HttpRequestInfo request) { AuthorizedTokenRequest message; this.Channel.TryReadFromRequest(request, out message); return message; } /// /// Prepares and sends an access token to a Consumer, and invalidates the request token. /// /// The Consumer's message requesting an access token. /// The HTTP response to actually send to the Consumer. public AuthorizedTokenResponse PrepareAccessTokenMessage(AuthorizedTokenRequest request) { 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 AuthorizedTokenResponse { AccessToken = accessToken, TokenSecret = tokenSecret, }; return 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 AccessProtectedResourceRequest ReadProtectedResourceAuthorization() { return this.ReadProtectedResourceAuthorization(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 AccessProtectedResourceRequest ReadProtectedResourceAuthorization(HttpRequest request) { return this.ReadProtectedResourceAuthorization(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 AccessProtectedResourceRequest ReadProtectedResourceAuthorization(HttpRequestMessageProperty request, Uri requestUri) { return this.ReadProtectedResourceAuthorization(new HttpRequestInfo(request, requestUri)); } /// /// 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 AccessProtectedResourceRequest ReadProtectedResourceAuthorization(HttpRequestInfo request) { if (request == null) { throw new ArgumentNullException("request"); } AccessProtectedResourceRequest 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; } /// /// Hooks the channel in order to perform some operations on some outgoing messages. /// /// The source of the event. /// The instance containing the event data. private void OAuthChannel_Sending(object sender, ChannelEventArgs e) { // Hook to store the token and secret on its way down to the Consumer. var grantRequestTokenResponse = e.Message as UnauthorizedTokenResponse; if (grantRequestTokenResponse != null) { this.TokenManager.StoreNewRequestToken(grantRequestTokenResponse.RequestMessage, grantRequestTokenResponse); } } } }