summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.OAuth.Consumer/OAuth/Consumer.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenAuth.OAuth.Consumer/OAuth/Consumer.cs')
-rw-r--r--src/DotNetOpenAuth.OAuth.Consumer/OAuth/Consumer.cs336
1 files changed, 336 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.OAuth.Consumer/OAuth/Consumer.cs b/src/DotNetOpenAuth.OAuth.Consumer/OAuth/Consumer.cs
new file mode 100644
index 0000000..578902c
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth.Consumer/OAuth/Consumer.cs
@@ -0,0 +1,336 @@
+//-----------------------------------------------------------------------
+// <copyright file="Consumer.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+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.Security.Cryptography.X509Certificates;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using System.Web;
+ using DotNetOpenAuth.Configuration;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
+ using DotNetOpenAuth.OAuth.ChannelElements;
+ using DotNetOpenAuth.OAuth.Messages;
+ using Validation;
+
+ /// <summary>
+ /// Base class for <see cref="WebConsumer"/> and <see cref="DesktopConsumer"/> types.
+ /// </summary>
+ public class Consumer {
+ /// <summary>
+ /// The host factories.
+ /// </summary>
+ private IHostFactories hostFactories;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Consumer"/> class.
+ /// </summary>
+ public Consumer() {
+ this.HostFactories = new DefaultOAuthHostFactories();
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Consumer"/> class.
+ /// </summary>
+ /// <param name="consumerKey">The consumer key.</param>
+ /// <param name="consumerSecret">The consumer secret.</param>
+ /// <param name="serviceProvider">The service provider.</param>
+ /// <param name="temporaryCredentialStorage">The temporary credential storage.</param>
+ public Consumer(
+ string consumerKey,
+ string consumerSecret,
+ ServiceProviderDescription serviceProvider,
+ ITemporaryCredentialStorage temporaryCredentialStorage) {
+ this.ConsumerKey = consumerKey;
+ this.ConsumerSecret = consumerSecret;
+ this.ServiceProvider = serviceProvider;
+ this.TemporaryCredentialStorage = temporaryCredentialStorage;
+ }
+
+ /// <summary>
+ /// Gets or sets the object with factories for host-customizable services.
+ /// </summary>
+ public IHostFactories HostFactories {
+ get {
+ return this.hostFactories;
+ }
+
+ set {
+ Requires.NotNull(value, "value");
+ this.hostFactories = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets the Consumer Key used to communicate with the Service Provider.
+ /// </summary>
+ public string ConsumerKey { get; set; }
+
+ /// <summary>
+ /// Gets or sets the consumer secret.
+ /// </summary>
+ /// <value>
+ /// The consumer secret.
+ /// </value>
+ public string ConsumerSecret { get; set; }
+
+ /// <summary>
+ /// Gets or sets the consumer certificate.
+ /// </summary>
+ /// <value>
+ /// The consumer certificate.
+ /// </value>
+ /// <remarks>
+ /// If set, this causes all outgoing messages to be signed with the certificate instead of the consumer secret.
+ /// </remarks>
+ public X509Certificate2 ConsumerCertificate { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Service Provider that will be accessed.
+ /// </summary>
+ public ServiceProviderDescription ServiceProvider { get; set; }
+
+ /// <summary>
+ /// Gets the persistence store for tokens and secrets.
+ /// </summary>
+ public ITemporaryCredentialStorage TemporaryCredentialStorage { get; set; }
+
+ /// <summary>
+ /// Obtains an access token for a new account at the Service Provider via 2-legged OAuth.
+ /// </summary>
+ /// <param name="requestParameters">Any applicable parameters to include in the query string of the token request.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>The access token.</returns>
+ public async Task<AccessTokenResponse> RequestNewClientAccountAsync(IEnumerable<KeyValuePair<string, string>> requestParameters = null, CancellationToken cancellationToken = default(CancellationToken)) {
+ Verify.Operation(this.ConsumerKey != null, Strings.RequiredPropertyNotYetPreset, "ConsumerKey");
+ Verify.Operation(this.ServiceProvider != null, Strings.RequiredPropertyNotYetPreset, "ServiceProvider");
+
+ using (var handler = this.CreateMessageHandler()) {
+ using (var client = this.CreateHttpClient(handler)) {
+ string identifier, secret;
+
+ var requestUri = new UriBuilder(this.ServiceProvider.TemporaryCredentialsRequestEndpoint);
+ requestUri.AppendQueryArgument(Protocol.CallbackParameter, "oob");
+ requestUri.AppendQueryArgs(requestParameters);
+ var request = new HttpRequestMessage(this.ServiceProvider.TemporaryCredentialsRequestEndpointMethod, requestUri.Uri);
+ using (var response = await client.SendAsync(request, cancellationToken)) {
+ response.EnsureSuccessStatusCode();
+ cancellationToken.ThrowIfCancellationRequested();
+
+ // Parse the response and ensure that it meets the requirements of the OAuth 1.0 spec.
+ string content = await response.Content.ReadAsStringAsync();
+ var responseData = HttpUtility.ParseQueryString(content);
+ identifier = responseData[Protocol.TokenParameter];
+ secret = responseData[Protocol.TokenSecretParameter];
+ ErrorUtilities.VerifyProtocol(!string.IsNullOrEmpty(identifier), MessagingStrings.RequiredParametersMissing, typeof(UnauthorizedTokenResponse).Name, Protocol.TokenParameter);
+ ErrorUtilities.VerifyProtocol(secret != null, MessagingStrings.RequiredParametersMissing, typeof(UnauthorizedTokenResponse).Name, Protocol.TokenSecretParameter);
+ }
+
+ // Immediately exchange the temporary credential for an access token.
+ handler.AccessToken = identifier;
+ handler.AccessTokenSecret = secret;
+ request = new HttpRequestMessage(this.ServiceProvider.TokenRequestEndpointMethod, this.ServiceProvider.TokenRequestEndpoint);
+ using (var response = await client.SendAsync(request, cancellationToken)) {
+ response.EnsureSuccessStatusCode();
+
+ // Parse the response and ensure that it meets the requirements of the OAuth 1.0 spec.
+ string content = await response.Content.ReadAsStringAsync();
+ var responseData = HttpUtility.ParseQueryString(content);
+ string accessToken = responseData[Protocol.TokenParameter];
+ string tokenSecret = responseData[Protocol.TokenSecretParameter];
+ ErrorUtilities.VerifyProtocol(!string.IsNullOrEmpty(accessToken), MessagingStrings.RequiredParametersMissing, typeof(AuthorizedTokenResponse).Name, Protocol.TokenParameter);
+ ErrorUtilities.VerifyProtocol(tokenSecret != null, MessagingStrings.RequiredParametersMissing, typeof(AuthorizedTokenResponse).Name, Protocol.TokenSecretParameter);
+
+ responseData.Remove(Protocol.TokenParameter);
+ responseData.Remove(Protocol.TokenSecretParameter);
+ return new AccessTokenResponse(accessToken, tokenSecret, responseData);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Prepares an OAuth message that begins an authorization request that will
+ /// redirect the user to the Service Provider to provide that authorization.
+ /// </summary>
+ /// <param name="callback">The absolute URI that the Service Provider should redirect the
+ /// User Agent to upon successful authorization, or <c>null</c> to signify an out of band return.</param>
+ /// <param name="requestParameters">Extra parameters to add to the request token message. Optional.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>
+ /// The URL to direct the user agent to for user authorization.
+ /// </returns>
+ public async Task<Uri> RequestUserAuthorizationAsync(Uri callback = null, IEnumerable<KeyValuePair<string, string>> requestParameters = null, CancellationToken cancellationToken = default(CancellationToken)) {
+ Requires.NotNull(callback, "callback");
+ Verify.Operation(this.ConsumerKey != null, Strings.RequiredPropertyNotYetPreset, "ConsumerKey");
+ Verify.Operation(this.TemporaryCredentialStorage != null, Strings.RequiredPropertyNotYetPreset, "TemporaryCredentialStorage");
+ Verify.Operation(this.ServiceProvider != null, Strings.RequiredPropertyNotYetPreset, "ServiceProvider");
+
+ // Obtain temporary credentials before the redirect.
+ using (var client = this.CreateHttpClient(new AccessToken())) {
+ var requestUri = new UriBuilder(this.ServiceProvider.TemporaryCredentialsRequestEndpoint);
+ requestUri.AppendQueryArgument(Protocol.CallbackParameter, callback != null ? callback.AbsoluteUri : "oob");
+ requestUri.AppendQueryArgs(requestParameters);
+ var request = new HttpRequestMessage(this.ServiceProvider.TemporaryCredentialsRequestEndpointMethod, requestUri.Uri);
+ using (var response = await client.SendAsync(request, cancellationToken)) {
+ response.EnsureSuccessStatusCode();
+ cancellationToken.ThrowIfCancellationRequested();
+
+ // Parse the response and ensure that it meets the requirements of the OAuth 1.0 spec.
+ string content = await response.Content.ReadAsStringAsync();
+ var responseData = HttpUtility.ParseQueryString(content);
+ ErrorUtilities.VerifyProtocol(string.Equals(responseData[Protocol.CallbackConfirmedParameter], "true", StringComparison.Ordinal), MessagingStrings.RequiredParametersMissing, typeof(UnauthorizedTokenResponse).Name, Protocol.CallbackConfirmedParameter);
+ string identifier = responseData[Protocol.TokenParameter];
+ string secret = responseData[Protocol.TokenSecretParameter];
+ ErrorUtilities.VerifyProtocol(!string.IsNullOrEmpty(identifier), MessagingStrings.RequiredParametersMissing, typeof(UnauthorizedTokenResponse).Name, Protocol.TokenParameter);
+ ErrorUtilities.VerifyProtocol(secret != null, MessagingStrings.RequiredParametersMissing, typeof(UnauthorizedTokenResponse).Name, Protocol.TokenSecretParameter);
+
+ // Save the temporary credential we received so that after user authorization
+ // we can use it to obtain the access token.
+ cancellationToken.ThrowIfCancellationRequested();
+ this.TemporaryCredentialStorage.SaveTemporaryCredential(identifier, secret);
+
+ // Construct the user authorization URL so our caller can direct a browser to it.
+ var authorizationEndpoint = new UriBuilder(this.ServiceProvider.ResourceOwnerAuthorizationEndpoint);
+ authorizationEndpoint.AppendQueryArgument(Protocol.TokenParameter, identifier);
+ return authorizationEndpoint.Uri;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Obtains an access token after a successful user authorization.
+ /// </summary>
+ /// <param name="authorizationCompleteUri">The URI used to redirect back to the consumer that contains a message from the service provider.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>
+ /// The access token assigned by the Service Provider, or <c>null</c> if no response was detected in the specified URL.
+ /// </returns>
+ public Task<AccessTokenResponse> ProcessUserAuthorizationAsync(Uri authorizationCompleteUri, CancellationToken cancellationToken = default(CancellationToken)) {
+ Requires.NotNull(authorizationCompleteUri, "authorizationCompleteUri");
+ Verify.Operation(this.TemporaryCredentialStorage != null, Strings.RequiredPropertyNotYetPreset, "TemporaryCredentialStorage");
+
+ // Parse the response and verify that it meets spec requirements.
+ var queryString = HttpUtility.ParseQueryString(authorizationCompleteUri.Query);
+ string identifier = queryString[Protocol.TokenParameter];
+ string verifier = queryString[Protocol.VerifierParameter];
+
+ if (identifier == null) {
+ // We assume there is no response message here at all, and return null to indicate that.
+ return null;
+ }
+
+ ErrorUtilities.VerifyProtocol(!string.IsNullOrEmpty(identifier), MessagingStrings.RequiredNonEmptyParameterWasEmpty, typeof(UserAuthorizationResponse).Name, Protocol.TokenParameter);
+ ErrorUtilities.VerifyProtocol(!string.IsNullOrEmpty(verifier), MessagingStrings.RequiredNonEmptyParameterWasEmpty, typeof(UserAuthorizationResponse).Name, Protocol.VerifierParameter);
+
+ var temporaryCredential = this.TemporaryCredentialStorage.RetrieveTemporaryCredential();
+ Verify.Operation(string.Equals(temporaryCredential.Key, identifier, StringComparison.Ordinal), "Temporary credential identifiers do not match.");
+
+ return this.ProcessUserAuthorizationAsync(verifier, cancellationToken);
+ }
+
+ /// <summary>
+ /// Obtains an access token after a successful user authorization.
+ /// </summary>
+ /// <param name="verifier">The verifier.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>
+ /// The access token assigned by the Service Provider.
+ /// </returns>
+ public async Task<AccessTokenResponse> ProcessUserAuthorizationAsync(string verifier, CancellationToken cancellationToken = default(CancellationToken)) {
+ Requires.NotNull(verifier, "verifier");
+ Verify.Operation(this.ConsumerKey != null, Strings.RequiredPropertyNotYetPreset, "ConsumerKey");
+ Verify.Operation(this.TemporaryCredentialStorage != null, Strings.RequiredPropertyNotYetPreset, "TemporaryCredentialStorage");
+ Verify.Operation(this.ServiceProvider != null, Strings.RequiredPropertyNotYetPreset, "ServiceProvider");
+
+ var temporaryCredential = this.TemporaryCredentialStorage.RetrieveTemporaryCredential();
+
+ using (var client = this.CreateHttpClient(new AccessToken(temporaryCredential.Key, temporaryCredential.Value))) {
+ var requestUri = new UriBuilder(this.ServiceProvider.TokenRequestEndpoint);
+ requestUri.AppendQueryArgument(Protocol.VerifierParameter, verifier);
+ var request = new HttpRequestMessage(this.ServiceProvider.TokenRequestEndpointMethod, requestUri.Uri);
+ using (var response = await client.SendAsync(request, cancellationToken)) {
+ response.EnsureSuccessStatusCode();
+
+ // Parse the response and ensure that it meets the requirements of the OAuth 1.0 spec.
+ string content = await response.Content.ReadAsStringAsync();
+ var responseData = HttpUtility.ParseQueryString(content);
+ string accessToken = responseData[Protocol.TokenParameter];
+ string tokenSecret = responseData[Protocol.TokenSecretParameter];
+ ErrorUtilities.VerifyProtocol(!string.IsNullOrEmpty(accessToken), MessagingStrings.RequiredParametersMissing, typeof(AuthorizedTokenResponse).Name, Protocol.TokenParameter);
+ ErrorUtilities.VerifyProtocol(tokenSecret != null, MessagingStrings.RequiredParametersMissing, typeof(AuthorizedTokenResponse).Name, Protocol.TokenSecretParameter);
+
+ responseData.Remove(Protocol.TokenParameter);
+ responseData.Remove(Protocol.TokenSecretParameter);
+ return new AccessTokenResponse(accessToken, tokenSecret, responseData);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Creates a message handler that signs outbound requests with a previously obtained authorization.
+ /// </summary>
+ /// <param name="accessToken">The access token to authorize outbound HTTP requests with.</param>
+ /// <param name="innerHandler">The inner handler that actually sends the HTTP message on the network.</param>
+ /// <returns>
+ /// A message handler.
+ /// </returns>
+ /// <remarks>
+ /// Overrides of this method may allow various derived types of handlers to be returned,
+ /// enabling consumers that use RSA or other signing methods.
+ /// </remarks>
+ public virtual OAuth1HttpMessageHandlerBase CreateMessageHandler(AccessToken accessToken = default(AccessToken), HttpMessageHandler innerHandler = null) {
+ Verify.Operation(this.ConsumerKey != null, Strings.RequiredPropertyNotYetPreset, "ConsumerKey");
+
+ innerHandler = innerHandler ?? this.HostFactories.CreateHttpMessageHandler();
+ OAuth1HttpMessageHandlerBase handler;
+ if (this.ConsumerCertificate != null) {
+ handler = new OAuth1RsaSha1HttpMessageHandler(innerHandler) {
+ SigningCertificate = this.ConsumerCertificate,
+ };
+ } else {
+ handler = new OAuth1HmacSha1HttpMessageHandler(innerHandler);
+ }
+
+ handler.ConsumerKey = this.ConsumerKey;
+ handler.ConsumerSecret = this.ConsumerSecret;
+ handler.AccessToken = accessToken.Token;
+ handler.AccessTokenSecret = accessToken.Secret;
+
+ return handler;
+ }
+
+ /// <summary>
+ /// Creates the HTTP client.
+ /// </summary>
+ /// <param name="accessToken">The access token to authorize outbound HTTP requests with.</param>
+ /// <param name="innerHandler">The inner handler that actually sends the HTTP message on the network.</param>
+ /// <returns>The HttpClient to use.</returns>
+ public HttpClient CreateHttpClient(AccessToken accessToken, HttpMessageHandler innerHandler = null) {
+ var handler = this.CreateMessageHandler(accessToken, innerHandler);
+ var client = this.HostFactories.CreateHttpClient(handler);
+ return client;
+ }
+
+ /// <summary>
+ /// Creates the HTTP client.
+ /// </summary>
+ /// <param name="innerHandler">The inner handler that actually sends the HTTP message on the network.</param>
+ /// <returns>The HttpClient to use.</returns>
+ public HttpClient CreateHttpClient(OAuth1HttpMessageHandlerBase innerHandler) {
+ Requires.NotNull(innerHandler, "innerHandler");
+
+ var client = this.HostFactories.CreateHttpClient(innerHandler);
+ return client;
+ }
+ }
+}