summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.OpenIdOAuth/OAuth
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenAuth.OpenIdOAuth/OAuth')
-rw-r--r--src/DotNetOpenAuth.OpenIdOAuth/OAuth/ChannelElements/ICombinedOpenIdProviderTokenManager.cs33
-rw-r--r--src/DotNetOpenAuth.OpenIdOAuth/OAuth/ChannelElements/IOpenIdOAuthTokenManager.cs30
-rw-r--r--src/DotNetOpenAuth.OpenIdOAuth/OAuth/ServiceProviderOpenIdProvider.cs161
-rw-r--r--src/DotNetOpenAuth.OpenIdOAuth/OAuth/WebConsumerOpenIdRelyingParty.cs97
4 files changed, 321 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.OpenIdOAuth/OAuth/ChannelElements/ICombinedOpenIdProviderTokenManager.cs b/src/DotNetOpenAuth.OpenIdOAuth/OAuth/ChannelElements/ICombinedOpenIdProviderTokenManager.cs
new file mode 100644
index 0000000..d6a7e93
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenIdOAuth/OAuth/ChannelElements/ICombinedOpenIdProviderTokenManager.cs
@@ -0,0 +1,33 @@
+//-----------------------------------------------------------------------
+// <copyright file="ICombinedOpenIdProviderTokenManager.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.ChannelElements {
+ using DotNetOpenAuth.OpenId;
+
+ /// <summary>
+ /// An interface that providers that play a dual role as OpenID Provider
+ /// and OAuth Service Provider should implement on their token manager classes.
+ /// </summary>
+ /// <remarks>
+ /// This interface should be implemented by the same class that implements
+ /// <see cref="ITokenManager"/> in order to enable the OpenID+OAuth extension.
+ /// </remarks>
+ public interface ICombinedOpenIdProviderTokenManager : IOpenIdOAuthTokenManager, ITokenManager {
+ /// <summary>
+ /// Gets the OAuth consumer key for a given OpenID relying party realm.
+ /// </summary>
+ /// <param name="realm">The relying party's OpenID realm.</param>
+ /// <returns>The OAuth consumer key for a given OpenID realm.</returns>
+ /// <para>This is a security-critical function. Since OpenID requests
+ /// and OAuth extensions for those requests can be formulated by ANYONE
+ /// (no signing is required by the relying party), and since the response to
+ /// the authentication will include access the user is granted to the
+ /// relying party who CLAIMS to be from some realm, it is of paramount
+ /// importance that the realm is recognized as belonging to the consumer
+ /// key by the host service provider in order to protect against phishers.</para>
+ string GetConsumerKey(Realm realm);
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenIdOAuth/OAuth/ChannelElements/IOpenIdOAuthTokenManager.cs b/src/DotNetOpenAuth.OpenIdOAuth/OAuth/ChannelElements/IOpenIdOAuthTokenManager.cs
new file mode 100644
index 0000000..3f3c1d9
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenIdOAuth/OAuth/ChannelElements/IOpenIdOAuthTokenManager.cs
@@ -0,0 +1,30 @@
+//-----------------------------------------------------------------------
+// <copyright file="IOpenIdOAuthTokenManager.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.ChannelElements {
+ using DotNetOpenAuth.OpenId;
+ using DotNetOpenAuth.OpenId.Extensions.OAuth;
+
+ /// <summary>
+ /// Additional methods an <see cref="ITokenManager"/> implementing class
+ /// may implement to support the OpenID+OAuth extension.
+ /// </summary>
+ public interface IOpenIdOAuthTokenManager {
+ /// <summary>
+ /// Stores a new request token obtained over an OpenID request.
+ /// </summary>
+ /// <param name="consumerKey">The consumer key.</param>
+ /// <param name="authorization">The authorization message carrying the request token and authorized access scope.</param>
+ /// <remarks>
+ /// <para>The token secret is the empty string.</para>
+ /// <para>Tokens stored by this method should be short-lived to mitigate
+ /// possible security threats. Their lifetime should be sufficient for the
+ /// relying party to receive the positive authentication assertion and immediately
+ /// send a follow-up request for the access token.</para>
+ /// </remarks>
+ void StoreOpenIdAuthorizedRequestToken(string consumerKey, AuthorizationApprovedResponse authorization);
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenIdOAuth/OAuth/ServiceProviderOpenIdProvider.cs b/src/DotNetOpenAuth.OpenIdOAuth/OAuth/ServiceProviderOpenIdProvider.cs
new file mode 100644
index 0000000..b590a90
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenIdOAuth/OAuth/ServiceProviderOpenIdProvider.cs
@@ -0,0 +1,161 @@
+//-----------------------------------------------------------------------
+// <copyright file="ServiceProviderOpenIdProvider.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth {
+ using System;
+ using System.Collections.Generic;
+ using System.ComponentModel;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.Security.Principal;
+ using System.ServiceModel.Channels;
+ using System.Web;
+ using DotNetOpenAuth.Configuration;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
+ using DotNetOpenAuth.OAuth.ChannelElements;
+ using DotNetOpenAuth.OAuth.Messages;
+ using DotNetOpenAuth.OpenId;
+ using DotNetOpenAuth.OpenId.Extensions.OAuth;
+ using DotNetOpenAuth.OpenId.Messages;
+ using DotNetOpenAuth.OpenId.Provider;
+
+ /// <summary>
+ /// A web application that allows access via OAuth and can respond to OpenID+OAuth requests.
+ /// </summary>
+ /// <remarks>
+ /// <para>The Service Provider’s documentation should include:</para>
+ /// <list>
+ /// <item>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.</item>
+ /// <item>Signature methods supported by the Service Provider.</item>
+ /// <item>Any additional request parameters that the Service Provider requires in order to obtain a Token. Service Provider specific parameters MUST NOT begin with oauth_.</item>
+ /// </list>
+ /// </remarks>
+ public class ServiceProviderOpenIdProvider : ServiceProvider {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ServiceProviderOpenIdProvider"/> class.
+ /// </summary>
+ /// <param name="serviceDescription">The endpoints and behavior on the Service Provider.</param>
+ /// <param name="tokenManager">The host's method of storing and recalling tokens and secrets.</param>
+ public ServiceProviderOpenIdProvider(ServiceProviderDescription serviceDescription, IServiceProviderTokenManager tokenManager)
+ : base(serviceDescription, tokenManager) {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ServiceProviderOpenIdProvider"/> class.
+ /// </summary>
+ /// <param name="serviceDescription">The service description.</param>
+ /// <param name="tokenManager">The token manager.</param>
+ /// <param name="messageTypeProvider">The message type provider.</param>
+ public ServiceProviderOpenIdProvider(ServiceProviderDescription serviceDescription, IServiceProviderTokenManager tokenManager, OAuthServiceProviderMessageFactory messageTypeProvider)
+ : base(serviceDescription, tokenManager, messageTypeProvider) {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ServiceProviderOpenIdProvider"/> class.
+ /// </summary>
+ /// <param name="serviceDescription">The service description.</param>
+ /// <param name="tokenManager">The token manager.</param>
+ /// <param name="nonceStore">The nonce store.</param>
+ public ServiceProviderOpenIdProvider(ServiceProviderDescription serviceDescription, IServiceProviderTokenManager tokenManager, INonceStore nonceStore)
+ : base(serviceDescription, tokenManager, nonceStore) {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ServiceProviderOpenIdProvider"/> class.
+ /// </summary>
+ /// <param name="serviceDescription">The service description.</param>
+ /// <param name="tokenManager">The token manager.</param>
+ /// <param name="nonceStore">The nonce store.</param>
+ /// <param name="messageTypeProvider">The message type provider.</param>
+ public ServiceProviderOpenIdProvider(ServiceProviderDescription serviceDescription, IServiceProviderTokenManager tokenManager, INonceStore nonceStore, OAuthServiceProviderMessageFactory messageTypeProvider)
+ : base(serviceDescription, tokenManager, nonceStore, messageTypeProvider) {
+ }
+
+ /// <summary>
+ /// Gets the OAuth authorization request included with an OpenID authentication
+ /// request, if there is one.
+ /// </summary>
+ /// <param name="openIdRequest">The OpenID authentication request.</param>
+ /// <returns>
+ /// The scope of access the relying party is requesting, or null if no OAuth request
+ /// is present.
+ /// </returns>
+ /// <remarks>
+ /// <para>Call this method rather than simply extracting the OAuth extension
+ /// out from the authentication request directly to ensure that the additional
+ /// security measures that are required are taken.</para>
+ /// </remarks>
+ public AuthorizationRequest ReadAuthorizationRequest(IHostProcessedRequest openIdRequest) {
+ Requires.NotNull(openIdRequest, "openIdRequest");
+ Requires.ValidState(this.TokenManager is ICombinedOpenIdProviderTokenManager);
+ var openidTokenManager = this.TokenManager as ICombinedOpenIdProviderTokenManager;
+ ErrorUtilities.VerifyOperation(openidTokenManager != null, OAuthStrings.OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface, typeof(IOpenIdOAuthTokenManager).FullName);
+
+ var authzRequest = openIdRequest.GetExtension<AuthorizationRequest>();
+ if (authzRequest == null) {
+ return null;
+ }
+
+ // OpenID+OAuth spec section 9:
+ // The Combined Provider SHOULD verify that the consumer key passed in the
+ // request is authorized to be used for the realm passed in the request.
+ string expectedConsumerKey = openidTokenManager.GetConsumerKey(openIdRequest.Realm);
+ ErrorUtilities.VerifyProtocol(
+ string.Equals(expectedConsumerKey, authzRequest.Consumer, StringComparison.Ordinal),
+ OAuthStrings.OpenIdOAuthRealmConsumerKeyDoNotMatch);
+
+ return authzRequest;
+ }
+
+ /// <summary>
+ /// Attaches the authorization response to an OpenID authentication response.
+ /// </summary>
+ /// <param name="openIdAuthenticationRequest">The OpenID authentication request.</param>
+ /// <param name="consumerKey">The consumer key. Must be <c>null</c> if and only if <paramref name="scope"/> is null.</param>
+ /// <param name="scope">The approved access scope. Use <c>null</c> to indicate no access was granted. The empty string will be interpreted as some default level of access is granted.</param>
+ [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "We want to take IAuthenticationRequest because that's the only supported use case.")]
+ [Obsolete("Call the overload that doesn't take a consumerKey instead.")]
+ public void AttachAuthorizationResponse(IHostProcessedRequest openIdAuthenticationRequest, string consumerKey, string scope) {
+ Requires.NotNull(openIdAuthenticationRequest, "openIdAuthenticationRequest");
+ Requires.True((consumerKey == null) == (scope == null), null);
+ Requires.ValidState(this.TokenManager is ICombinedOpenIdProviderTokenManager);
+ var openidTokenManager = (ICombinedOpenIdProviderTokenManager)this.TokenManager;
+ ErrorUtilities.VerifyArgument(consumerKey == null || consumerKey == openidTokenManager.GetConsumerKey(openIdAuthenticationRequest.Realm), OAuthStrings.OpenIdOAuthRealmConsumerKeyDoNotMatch);
+
+ this.AttachAuthorizationResponse(openIdAuthenticationRequest, scope);
+ }
+
+ /// <summary>
+ /// Attaches the authorization response to an OpenID authentication response.
+ /// </summary>
+ /// <param name="openIdAuthenticationRequest">The OpenID authentication request.</param>
+ /// <param name="scope">The approved access scope. Use <c>null</c> to indicate no access was granted. The empty string will be interpreted as some default level of access is granted.</param>
+ [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "We want to take IAuthenticationRequest because that's the only supported use case.")]
+ public void AttachAuthorizationResponse(IHostProcessedRequest openIdAuthenticationRequest, string scope) {
+ Requires.NotNull(openIdAuthenticationRequest, "openIdAuthenticationRequest");
+ Requires.ValidState(this.TokenManager is ICombinedOpenIdProviderTokenManager);
+
+ var openidTokenManager = this.TokenManager as ICombinedOpenIdProviderTokenManager;
+ IOpenIdMessageExtension response;
+ if (scope != null) {
+ // Generate an authorized request token to return to the relying party.
+ string consumerKey = openidTokenManager.GetConsumerKey(openIdAuthenticationRequest.Realm);
+ var approvedResponse = new AuthorizationApprovedResponse {
+ RequestToken = this.TokenGenerator.GenerateRequestToken(consumerKey),
+ Scope = scope,
+ };
+ openidTokenManager.StoreOpenIdAuthorizedRequestToken(consumerKey, approvedResponse);
+ response = approvedResponse;
+ } else {
+ response = new AuthorizationDeclinedResponse();
+ }
+
+ openIdAuthenticationRequest.AddResponseExtension(response);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenIdOAuth/OAuth/WebConsumerOpenIdRelyingParty.cs b/src/DotNetOpenAuth.OpenIdOAuth/OAuth/WebConsumerOpenIdRelyingParty.cs
new file mode 100644
index 0000000..0d1a602
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenIdOAuth/OAuth/WebConsumerOpenIdRelyingParty.cs
@@ -0,0 +1,97 @@
+//-----------------------------------------------------------------------
+// <copyright file="WebConsumerOpenIdRelyingParty.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OAuth.ChannelElements;
+ using DotNetOpenAuth.OAuth.Messages;
+ using DotNetOpenAuth.OpenId.Extensions.OAuth;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+
+ /// <summary>
+ /// A website or application that uses OAuth to access the Service Provider on behalf of the User
+ /// and can attach OAuth requests to outbound OpenID authentication requests.
+ /// </summary>
+ /// <remarks>
+ /// The methods on this class are thread-safe. Provided the properties are set and not changed
+ /// afterward, a single instance of this class may be used by an entire web application safely.
+ /// </remarks>
+ public class WebConsumerOpenIdRelyingParty : WebConsumer {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="WebConsumerOpenIdRelyingParty"/> class.
+ /// </summary>
+ /// <param name="serviceDescription">The endpoints and behavior of the Service Provider.</param>
+ /// <param name="tokenManager">The host's method of storing and recalling tokens and secrets.</param>
+ public WebConsumerOpenIdRelyingParty(ServiceProviderDescription serviceDescription, IConsumerTokenManager tokenManager)
+ : base(serviceDescription, tokenManager) {
+ }
+
+ /// <summary>
+ /// Attaches an OAuth authorization request to an outgoing OpenID authentication request.
+ /// </summary>
+ /// <param name="openIdAuthenticationRequest">The OpenID authentication request.</param>
+ /// <param name="scope">The scope of access that is requested of the service provider.</param>
+ public void AttachAuthorizationRequest(IAuthenticationRequest openIdAuthenticationRequest, string scope) {
+ Requires.NotNull(openIdAuthenticationRequest, "openIdAuthenticationRequest");
+
+ var authorizationRequest = new AuthorizationRequest {
+ Consumer = this.ConsumerKey,
+ Scope = scope,
+ };
+
+ openIdAuthenticationRequest.AddExtension(authorizationRequest);
+ }
+
+ /// <summary>
+ /// Processes an incoming authorization-granted message from an SP and obtains an access token.
+ /// </summary>
+ /// <param name="openIdAuthenticationResponse">The OpenID authentication response that may be carrying an authorized request token.</param>
+ /// <returns>
+ /// The access token, or null if OAuth authorization was denied by the user or service provider.
+ /// </returns>
+ /// <remarks>
+ /// The access token, if granted, is automatically stored in the <see cref="ConsumerBase.TokenManager"/>.
+ /// The token manager instance must implement <see cref="IOpenIdOAuthTokenManager"/>.
+ /// </remarks>
+ public AuthorizedTokenResponse ProcessUserAuthorization(IAuthenticationResponse openIdAuthenticationResponse) {
+ Requires.NotNull(openIdAuthenticationResponse, "openIdAuthenticationResponse");
+ Requires.ValidState(this.TokenManager is IOpenIdOAuthTokenManager);
+ var openidTokenManager = this.TokenManager as IOpenIdOAuthTokenManager;
+ ErrorUtilities.VerifyOperation(openidTokenManager != null, OAuthStrings.OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface, typeof(IOpenIdOAuthTokenManager).FullName);
+
+ // The OAuth extension is only expected in positive assertion responses.
+ if (openIdAuthenticationResponse.Status != AuthenticationStatus.Authenticated) {
+ return null;
+ }
+
+ // Retrieve the OAuth extension
+ var positiveAuthorization = openIdAuthenticationResponse.GetExtension<AuthorizationApprovedResponse>();
+ if (positiveAuthorization == null) {
+ return null;
+ }
+
+ // Prepare a message to exchange the request token for an access token.
+ // We are careful to use a v1.0 message version so that the oauth_verifier is not required.
+ var requestAccess = new AuthorizedTokenRequest(this.ServiceProvider.AccessTokenEndpoint, Protocol.V10.Version) {
+ RequestToken = positiveAuthorization.RequestToken,
+ ConsumerKey = this.ConsumerKey,
+ };
+
+ // Retrieve the access token and store it in the token manager.
+ openidTokenManager.StoreOpenIdAuthorizedRequestToken(this.ConsumerKey, positiveAuthorization);
+ var grantAccess = this.Channel.Request<AuthorizedTokenResponse>(requestAccess);
+ this.TokenManager.ExpireRequestTokenAndStoreNewAccessToken(this.ConsumerKey, positiveAuthorization.RequestToken, grantAccess.AccessToken, grantAccess.TokenSecret);
+
+ // Provide the caller with the access token so it may be associated with the user
+ // that is logging in.
+ return grantAccess;
+ }
+ }
+}