//----------------------------------------------------------------------- // // Copyright (c) Outercurve Foundation. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOpenAuth.OAuth { using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using DotNetOpenAuth.Configuration; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.OAuth.ChannelElements; using DotNetOpenAuth.OAuth.Messages; using Validation; /// /// Base class for and types. /// public class ConsumerBase : IDisposable { /// /// Initializes a new instance of the class. /// /// The endpoints and behavior of the Service Provider. /// The host's method of storing and recalling tokens and secrets. protected ConsumerBase(ServiceProviderDescription serviceDescription, IConsumerTokenManager tokenManager) { Requires.NotNull(serviceDescription, "serviceDescription"); Requires.NotNull(tokenManager, "tokenManager"); ITamperProtectionChannelBindingElement signingElement = serviceDescription.CreateTamperProtectionElement(); INonceStore store = new NonceMemoryStore(StandardExpirationBindingElement.MaximumMessageAge); this.SecuritySettings = OAuthElement.Configuration.Consumer.SecuritySettings.CreateSecuritySettings(); this.OAuthChannel = new OAuthConsumerChannel(signingElement, store, tokenManager, this.SecuritySettings); this.ServiceProvider = serviceDescription; OAuthReporting.RecordFeatureAndDependencyUse(this, serviceDescription, tokenManager, null); } /// /// Gets the Consumer Key used to communicate with the Service Provider. /// public string ConsumerKey { get { return this.TokenManager.ConsumerKey; } } /// /// Gets the Service Provider that will be accessed. /// public ServiceProviderDescription ServiceProvider { get; private set; } /// /// Gets the persistence store for tokens and secrets. /// public IConsumerTokenManager TokenManager { get { return (IConsumerTokenManager)this.OAuthChannel.TokenManager; } } /// /// Gets the channel to use for sending/receiving messages. /// public Channel Channel { get { return this.OAuthChannel; } } /// /// Gets the security settings for this consumer. /// internal ConsumerSecuritySettings SecuritySettings { get; private set; } /// /// Gets or sets the channel to use for sending/receiving messages. /// internal OAuthChannel OAuthChannel { get; set; } /// /// Creates a message handler that signs outbound requests with a previously obtained authorization. /// /// The access token to authorize outbound HTTP requests with. /// The inner handler that actually sends the HTTP message on the network. /// /// A message handler. /// public OAuth1HttpMessageHandlerBase CreateMessageHandler(string accessToken = null, HttpMessageHandler innerHandler = null) { return new OAuth1HmacSha1HttpMessageHandler() { ConsumerKey = this.ConsumerKey, ConsumerSecret = this.TokenManager.ConsumerSecret, AccessToken = accessToken, AccessTokenSecret = accessToken != null ? this.TokenManager.GetTokenSecret(accessToken) : null, InnerHandler = innerHandler ?? this.Channel.HostFactories.CreateHttpMessageHandler(), }; } /// /// Creates the HTTP client. /// /// The access token to authorize outbound HTTP requests with. /// The inner handler that actually sends the HTTP message on the network. /// The HttpClient to use. public HttpClient CreateHttpClient(string accessToken, HttpMessageHandler innerHandler = null) { Requires.NotNullOrEmpty(accessToken, "accessToken"); var handler = this.CreateMessageHandler(accessToken, innerHandler); var client = this.Channel.HostFactories.CreateHttpClient(handler); return client; } /// /// Obtains an access token for a new account at the Service Provider via 2-legged OAuth. /// /// Any applicable parameters to include in the query string of the token request. /// The cancellation token. /// The access token. /// /// The token secret is stored in the . /// public async Task RequestNewClientAccountAsync(IDictionary requestParameters = null, CancellationToken cancellationToken = default(CancellationToken)) { // Obtain an unauthorized request token. Force use of OAuth 1.0 (not 1.0a) so that // we are not expected to provide an oauth_verifier which doesn't apply in 2-legged OAuth. var token = new UnauthorizedTokenRequest(this.ServiceProvider.RequestTokenEndpoint, Protocol.V10.Version) { ConsumerKey = this.ConsumerKey, }; var tokenAccessor = this.Channel.MessageDescriptions.GetAccessor(token); tokenAccessor.AddExtraParameters(requestParameters); var requestTokenResponse = await this.Channel.RequestAsync(token, cancellationToken); this.TokenManager.StoreNewRequestToken(token, requestTokenResponse); var requestAccess = new AuthorizedTokenRequest(this.ServiceProvider.AccessTokenEndpoint, Protocol.V10.Version) { RequestToken = requestTokenResponse.RequestToken, ConsumerKey = this.ConsumerKey, }; var grantAccess = await this.Channel.RequestAsync(requestAccess, cancellationToken); this.TokenManager.ExpireRequestTokenAndStoreNewAccessToken(this.ConsumerKey, requestTokenResponse.RequestToken, grantAccess.AccessToken, grantAccess.TokenSecret); return grantAccess.AccessToken; } /// /// Creates a web request prepared with OAuth authorization /// that may be further tailored by adding parameters by the caller. /// /// The URL and method on the Service Provider to send the request to. /// The access token that permits access to the protected resource. /// The cancellation token. /// The initialized WebRequest object. public Task PrepareAuthorizedRequestAsync(MessageReceivingEndpoint endpoint, string accessToken, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNull(endpoint, "endpoint"); Requires.NotNullOrEmpty(accessToken, "accessToken"); return this.PrepareAuthorizedRequestAsync(endpoint, accessToken, EmptyDictionary.Instance, cancellationToken); } /// /// Creates a web request prepared with OAuth authorization /// that may be further tailored by adding parameters by the caller. /// /// The URL and method on the Service Provider to send the request to. /// The access token that permits access to the protected resource. /// Extra parameters to include in the message. Must not be null, but may be empty. /// The cancellation token. /// The initialized WebRequest object. public Task PrepareAuthorizedRequestAsync(MessageReceivingEndpoint endpoint, string accessToken, IDictionary extraData, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNull(endpoint, "endpoint"); Requires.NotNullOrEmpty(accessToken, "accessToken"); Requires.NotNull(extraData, "extraData"); IDirectedProtocolMessage message = this.CreateAuthorizingMessage(endpoint, accessToken); foreach (var pair in extraData) { message.ExtraData.Add(pair); } return this.OAuthChannel.InitializeRequestAsync(message, cancellationToken); } /// /// Prepares an authorized request that carries an HTTP multi-part POST, allowing for binary data. /// /// The URL and method on the Service Provider to send the request to. /// The access token that permits access to the protected resource. /// Extra parameters to include in the message. Must not be null, but may be empty. /// The cancellation token. /// The initialized WebRequest object. public Task PrepareAuthorizedRequestAsync(MessageReceivingEndpoint endpoint, string accessToken, IEnumerable binaryData, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNull(endpoint, "endpoint"); Requires.NotNullOrEmpty(accessToken, "accessToken"); Requires.NotNull(binaryData, "binaryData"); AccessProtectedResourceRequest message = this.CreateAuthorizingMessage(endpoint, accessToken); message.BinaryData.AddRange(binaryData); return this.OAuthChannel.InitializeRequestAsync(message, cancellationToken); } /// /// Prepares an HTTP request that has OAuth authorization already attached to it. /// /// The OAuth authorization message to attach to the HTTP request. /// The cancellation token. /// /// The HttpWebRequest that can be used to send the HTTP request to the remote service provider. /// /// /// If property on the /// has the /// flag set and /// is set to an HTTP method /// that includes an entity body, the request stream is automatically sent /// if and only if the dictionary is non-empty. /// [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Type of parameter forces the method to apply only to specific scenario.")] public Task PrepareAuthorizedRequestAsync(AccessProtectedResourceRequest message, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNull(message, "message"); return this.OAuthChannel.InitializeRequestAsync(message, cancellationToken); } #region IDisposable Members /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } #endregion /// /// Creates a web request prepared with OAuth authorization /// that may be further tailored by adding parameters by the caller. /// /// The URL and method on the Service Provider to send the request to. /// The access token that permits access to the protected resource. /// The initialized WebRequest object. protected internal AccessProtectedResourceRequest CreateAuthorizingMessage(MessageReceivingEndpoint endpoint, string accessToken) { Requires.NotNull(endpoint, "endpoint"); Requires.NotNullOrEmpty(accessToken, "accessToken"); AccessProtectedResourceRequest message = new AccessProtectedResourceRequest(endpoint, this.ServiceProvider.Version) { AccessToken = accessToken, ConsumerKey = this.ConsumerKey, }; return message; } /// /// Prepares an OAuth message that begins an authorization request that will /// redirect the user to the Service Provider to provide that authorization. /// /// An optional Consumer URL that the Service Provider should redirect the /// User Agent to upon successful authorization. /// Extra parameters to add to the request token message. Optional. /// Extra parameters to add to the redirect to Service Provider message. Optional. /// The cancellation token. /// /// The pending user agent redirect based message to be sent as an HttpResponse. /// [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "3#", Justification = "Two results")] protected internal async Task PrepareRequestUserAuthorizationAsync(Uri callback, IDictionary requestParameters, IDictionary redirectParameters, CancellationToken cancellationToken = default(CancellationToken)) { // Obtain an unauthorized request token. Assume the OAuth version given in the service description. var token = new UnauthorizedTokenRequest(this.ServiceProvider.RequestTokenEndpoint, this.ServiceProvider.Version) { ConsumerKey = this.ConsumerKey, Callback = callback, }; var tokenAccessor = this.Channel.MessageDescriptions.GetAccessor(token); tokenAccessor.AddExtraParameters(requestParameters); var requestTokenResponse = await this.Channel.RequestAsync(token, cancellationToken); this.TokenManager.StoreNewRequestToken(token, requestTokenResponse); // Fine-tune our understanding of the SP's supported OAuth version if it's wrong. if (this.ServiceProvider.Version != requestTokenResponse.Version) { Logger.OAuth.WarnFormat("Expected OAuth service provider at endpoint {0} to use OAuth {1} but {2} was detected. Adjusting service description to new version.", this.ServiceProvider.RequestTokenEndpoint.Location, this.ServiceProvider.Version, requestTokenResponse.Version); this.ServiceProvider.ProtocolVersion = Protocol.Lookup(requestTokenResponse.Version).ProtocolVersion; } // Request user authorization. The OAuth version will automatically include // or drop the callback that we're setting here. ITokenContainingMessage assignedRequestToken = requestTokenResponse; var requestAuthorization = new UserAuthorizationRequest(this.ServiceProvider.UserAuthorizationEndpoint, assignedRequestToken.Token, requestTokenResponse.Version) { Callback = callback, }; var requestAuthorizationAccessor = this.Channel.MessageDescriptions.GetAccessor(requestAuthorization); requestAuthorizationAccessor.AddExtraParameters(redirectParameters); return requestAuthorization; } /// /// Exchanges a given request token for access token. /// /// The request token that the user has authorized. /// The verifier code. /// The cancellation token. /// /// The access token assigned by the Service Provider. /// protected async Task ProcessUserAuthorizationAsync(string requestToken, string verifier, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNullOrEmpty(requestToken, "requestToken"); var requestAccess = new AuthorizedTokenRequest(this.ServiceProvider.AccessTokenEndpoint, this.ServiceProvider.Version) { RequestToken = requestToken, VerificationCode = verifier, ConsumerKey = this.ConsumerKey, }; var grantAccess = await this.Channel.RequestAsync(requestAccess, cancellationToken); this.TokenManager.ExpireRequestTokenAndStoreNewAccessToken(this.ConsumerKey, requestToken, grantAccess.AccessToken, grantAccess.TokenSecret); return grantAccess; } /// /// Releases unmanaged and - optionally - managed resources /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool disposing) { if (disposing) { this.Channel.Dispose(); } } } }