diff options
Diffstat (limited to 'src/DotNetOpenAuth.OAuth2.Client/OAuth2')
6 files changed, 312 insertions, 16 deletions
diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/IOAuth2ChannelWithClient.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/IOAuth2ChannelWithClient.cs new file mode 100644 index 0000000..c802be6 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/IOAuth2ChannelWithClient.cs @@ -0,0 +1,27 @@ +//----------------------------------------------------------------------- +// <copyright file="IOAuth2ChannelWithClient.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.ChannelElements { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + /// <summary> + /// An interface that defines the OAuth2 client specific channel additions. + /// </summary> + internal interface IOAuth2ChannelWithClient { + /// <summary> + /// Gets or sets the identifier by which this client is known to the Authorization Server. + /// </summary> + string ClientIdentifier { get; set; } + + /// <summary> + /// Gets or sets the client credentials applicator extension to use. + /// </summary> + ClientCredentialApplicator ClientCredentialApplicator { get; set; } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/OAuth2ClientChannel.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/OAuth2ClientChannel.cs index 54fc110..8ad2ed9 100644 --- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/OAuth2ClientChannel.cs +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/OAuth2ClientChannel.cs @@ -18,7 +18,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// <summary> /// The messaging channel used by OAuth 2.0 Clients. /// </summary> - internal class OAuth2ClientChannel : OAuth2ChannelBase { + internal class OAuth2ClientChannel : OAuth2ChannelBase, IOAuth2ChannelWithClient { /// <summary> /// The messages receivable by this channel. /// </summary> @@ -34,10 +34,22 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// <summary> /// Initializes a new instance of the <see cref="OAuth2ClientChannel"/> class. /// </summary> - internal OAuth2ClientChannel() : base(MessageTypes) { + internal OAuth2ClientChannel() + : base(MessageTypes) { } /// <summary> + /// Gets or sets the identifier by which this client is known to the Authorization Server. + /// </summary> + public string ClientIdentifier { get; set; } + + /// <summary> + /// Gets or sets the tool to use to apply client credentials to authenticated requests to the Authorization Server. + /// </summary> + /// <value>May be <c>null</c> if this client has no client secret.</value> + public ClientCredentialApplicator ClientCredentialApplicator { get; set; } + + /// <summary> /// Prepares an HTTP request that carries a given message. /// </summary> /// <param name="request">The message to send.</param> @@ -131,5 +143,17 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { // Clients don't ever send direct responses. throw new NotImplementedException(); } + + /// <summary> + /// Performs additional processing on an outgoing web request before it is sent to the remote server. + /// </summary> + /// <param name="request">The request.</param> + protected override void PrepareHttpWebRequest(HttpWebRequest request) { + base.PrepareHttpWebRequest(request); + + if (this.ClientCredentialApplicator != null) { + this.ClientCredentialApplicator.ApplyClientCredential(this.ClientIdentifier, request); + } + } } } diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs index 77b9cc6..5f377ae 100644 --- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs @@ -26,13 +26,16 @@ namespace DotNetOpenAuth.OAuth2 { /// </summary> /// <param name="authorizationServer">The token issuer.</param> /// <param name="clientIdentifier">The client identifier.</param> - /// <param name="clientSecret">The client secret.</param> - protected ClientBase(AuthorizationServerDescription authorizationServer, string clientIdentifier = null, string clientSecret = null) { + /// <param name="clientCredentialApplicator"> + /// The tool to use to apply client credentials to authenticated requests to the Authorization Server. + /// May be <c>null</c> for clients with no secret or other means of authentication. + /// </param> + protected ClientBase(AuthorizationServerDescription authorizationServer, string clientIdentifier = null, ClientCredentialApplicator clientCredentialApplicator = null) { Requires.NotNull(authorizationServer, "authorizationServer"); this.AuthorizationServer = authorizationServer; this.Channel = new OAuth2ClientChannel(); this.ClientIdentifier = clientIdentifier; - this.ClientSecret = clientSecret; + this.ClientCredentialApplicator = clientCredentialApplicator; } /// <summary> @@ -50,12 +53,26 @@ namespace DotNetOpenAuth.OAuth2 { /// <summary> /// Gets or sets the identifier by which this client is known to the Authorization Server. /// </summary> - public string ClientIdentifier { get; set; } + public string ClientIdentifier { + get { return this.OAuthChannel.ClientIdentifier; } + set { this.OAuthChannel.ClientIdentifier = value; } + } + + /// <summary> + /// Gets or sets the tool to use to apply client credentials to authenticated requests to the Authorization Server. + /// </summary> + /// <value>May be <c>null</c> if this client has no client secret.</value> + public ClientCredentialApplicator ClientCredentialApplicator { + get { return this.OAuthChannel.ClientCredentialApplicator; } + set { this.OAuthChannel.ClientCredentialApplicator = value; } + } /// <summary> - /// Gets or sets the client secret shared with the Authorization Server. + /// Gets the OAuth client channel. /// </summary> - public string ClientSecret { get; set; } + internal IOAuth2ChannelWithClient OAuthChannel { + get { return (IOAuth2ChannelWithClient)this.Channel; } + } /// <summary> /// Adds the necessary HTTP Authorization header to an HTTP request for protected resources @@ -118,10 +135,11 @@ namespace DotNetOpenAuth.OAuth2 { var request = new AccessTokenRefreshRequestC(this.AuthorizationServer) { ClientIdentifier = this.ClientIdentifier, - ClientSecret = this.ClientSecret, RefreshToken = authorization.RefreshToken, }; + this.ApplyClientCredential(request); + var response = this.Channel.Request<AccessTokenSuccessResponse>(request); UpdateAuthorizationWithResponse(authorization, response); return true; @@ -145,10 +163,11 @@ namespace DotNetOpenAuth.OAuth2 { var request = new AccessTokenRefreshRequestC(this.AuthorizationServer) { ClientIdentifier = this.ClientIdentifier, - ClientSecret = this.ClientSecret, RefreshToken = refreshToken, }; + this.ApplyClientCredential(request); + var response = this.Channel.Request<AccessTokenSuccessResponse>(request); var authorization = new AuthorizationState(); UpdateAuthorizationWithResponse(authorization, response); @@ -250,10 +269,10 @@ namespace DotNetOpenAuth.OAuth2 { var accessTokenRequest = new AccessTokenAuthorizationCodeRequestC(this.AuthorizationServer) { ClientIdentifier = this.ClientIdentifier, - ClientSecret = this.ClientSecret, Callback = authorizationState.Callback, AuthorizationCode = authorizationSuccess.AuthorizationCode, }; + this.ApplyClientCredential(accessTokenRequest); IProtocolMessage accessTokenResponse = this.Channel.Request(accessTokenRequest); var accessTokenSuccess = accessTokenResponse as AccessTokenSuccessResponse; var failedAccessTokenResponse = accessTokenResponse as AccessTokenFailedResponse; @@ -267,6 +286,27 @@ namespace DotNetOpenAuth.OAuth2 { } /// <summary> + /// Applies the default client authentication mechanism given a client secret. + /// </summary> + /// <param name="secret">The client secret. May be <c>null</c></param> + /// <returns>The client credential applicator.</returns> + protected static ClientCredentialApplicator DefaultSecretApplicator(string secret) { + return secret == null ? ClientCredentialApplicator.NoSecret() : ClientCredentialApplicator.NetworkCredential(secret); + } + + /// <summary> + /// Applies any applicable client credential to an authenticated outbound request to the authorization server. + /// </summary> + /// <param name="request">The request to apply authentication information to.</param> + protected void ApplyClientCredential(AuthenticatedClientRequestBase request) { + Requires.NotNull(request, "request"); + + if (this.ClientCredentialApplicator != null) { + this.ClientCredentialApplicator.ApplyClientCredential(this.ClientIdentifier, request); + } + } + + /// <summary> /// Calculates the fraction of life remaining in an access token. /// </summary> /// <param name="authorization">The authorization to measure.</param> @@ -295,7 +335,7 @@ namespace DotNetOpenAuth.OAuth2 { var authorizationState = new AuthorizationState(scopes); request.ClientIdentifier = this.ClientIdentifier; - request.ClientSecret = this.ClientSecret; + this.ApplyClientCredential(request); request.Scope.UnionWith(authorizationState.Scope); var response = this.Channel.Request(request); diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientCredentialApplicator.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientCredentialApplicator.cs new file mode 100644 index 0000000..2a9a8a7 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientCredentialApplicator.cs @@ -0,0 +1,165 @@ +//----------------------------------------------------------------------- +// <copyright file="ClientCredentialApplicator.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2 { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth2.Messages; + + /// <summary> + /// A base class for extensions that apply client authentication to messages for the authorization server in specific ways. + /// </summary> + public abstract class ClientCredentialApplicator { + /// <summary> + /// Initializes a new instance of the <see cref="ClientCredentialApplicator"/> class. + /// </summary> + protected ClientCredentialApplicator() { + } + + /// <summary> + /// Transmits the secret the client shares with the authorization server as a parameter in the POST entity payload. + /// </summary> + /// <param name="clientSecret">The secret the client shares with the authorization server.</param> + /// <returns>The credential applicator to provide to the <see cref="ClientBase"/> instance.</returns> + public static ClientCredentialApplicator PostParameter(string clientSecret) { + Requires.NotNullOrEmpty(clientSecret, "clientSecret"); + return new PostParameterApplicator(clientSecret); + } + + /// <summary> + /// Transmits the client identifier and secret in the HTTP Authorization header via HTTP Basic authentication. + /// </summary> + /// <param name="credential">The client id and secret.</param> + /// <returns>The credential applicator to provide to the <see cref="ClientBase"/> instance.</returns> + public static ClientCredentialApplicator NetworkCredential(NetworkCredential credential) { + Requires.NotNull(credential, "credential"); + return new NetworkCredentialApplicator(credential); + } + + /// <summary> + /// Transmits the client identifier and secret in the HTTP Authorization header via HTTP Basic authentication. + /// </summary> + /// <param name="clientSecret">The secret the client shares with the authorization server.</param> + /// <returns>The credential applicator to provide to the <see cref="ClientBase"/> instance.</returns> + public static ClientCredentialApplicator NetworkCredential(string clientSecret) { + Requires.NotNullOrEmpty(clientSecret, "clientSecret"); + return new NetworkCredentialApplicator(clientSecret); + } + + /// <summary> + /// Never transmits a secret. Useful for anonymous clients or clients unable to keep a secret. + /// </summary> + /// <returns>The credential applicator to provide to the <see cref="ClientBase"/> instance.</returns> + public static ClientCredentialApplicator NoSecret() { + return null; + } + + /// <summary> + /// Applies the client identifier and (when applicable) the client authentication to an outbound message. + /// </summary> + /// <param name="clientIdentifier">The identifier by which the authorization server should recognize this client.</param> + /// <param name="request">The outbound message to apply authentication information to.</param> + public virtual void ApplyClientCredential(string clientIdentifier, AuthenticatedClientRequestBase request) { + } + + /// <summary> + /// Applies the client identifier and (when applicable) the client authentication to an outbound message. + /// </summary> + /// <param name="clientIdentifier">The identifier by which the authorization server should recognize this client.</param> + /// <param name="request">The outbound message to apply authentication information to.</param> + public virtual void ApplyClientCredential(string clientIdentifier, HttpWebRequest request) { + } + + /// <summary> + /// Authenticates the client via HTTP Basic. + /// </summary> + private class NetworkCredentialApplicator : ClientCredentialApplicator { + /// <summary> + /// The client identifier and secret. + /// </summary> + private readonly NetworkCredential credential; + + /// <summary> + /// The client secret. + /// </summary> + private readonly string clientSecret; + + /// <summary> + /// Initializes a new instance of the <see cref="NetworkCredentialApplicator"/> class. + /// </summary> + /// <param name="clientSecret">The client secret.</param> + internal NetworkCredentialApplicator(string clientSecret) { + Requires.NotNullOrEmpty(clientSecret, "clientSecret"); + this.clientSecret = clientSecret; + } + + /// <summary> + /// Initializes a new instance of the <see cref="NetworkCredentialApplicator"/> class. + /// </summary> + /// <param name="credential">The client credential.</param> + internal NetworkCredentialApplicator(NetworkCredential credential) { + Requires.NotNull(credential, "credential"); + this.credential = credential; + } + + /// <summary> + /// Applies the client identifier and (when applicable) the client authentication to an outbound message. + /// </summary> + /// <param name="clientIdentifier">The identifier by which the authorization server should recognize this client.</param> + /// <param name="request">The outbound message to apply authentication information to.</param> + public override void ApplyClientCredential(string clientIdentifier, AuthenticatedClientRequestBase request) { + // When using network credentials, the client authentication is not done as standard message parts. + request.ClientIdentifier = null; + request.ClientSecret = null; + } + + /// <summary> + /// Applies the client identifier and (when applicable) the client authentication to an outbound message. + /// </summary> + /// <param name="clientIdentifier">The identifier by which the authorization server should recognize this client.</param> + /// <param name="request">The outbound message to apply authentication information to.</param> + public override void ApplyClientCredential(string clientIdentifier, HttpWebRequest request) { + if (this.credential != null && this.credential.UserName == clientIdentifier) { + ErrorUtilities.VerifyHost(false, "Client identifiers \"{0}\" and \"{1}\" do not match.", this.credential.UserName, clientIdentifier); + } + + request.Credentials = this.credential ?? new NetworkCredential(clientIdentifier, this.clientSecret); + } + } + + /// <summary> + /// Authenticates the client via a client_secret parameter in the message. + /// </summary> + private class PostParameterApplicator : ClientCredentialApplicator { + /// <summary> + /// The client secret. + /// </summary> + private readonly string secret; + + /// <summary> + /// Initializes a new instance of the <see cref="PostParameterApplicator"/> class. + /// </summary> + /// <param name="clientSecret">The client secret.</param> + internal PostParameterApplicator(string clientSecret) { + Requires.NotNullOrEmpty(clientSecret, "clientSecret"); + this.secret = clientSecret; + } + + /// <summary> + /// Applies the client identifier and (when applicable) the client authentication to an outbound message. + /// </summary> + /// <param name="clientIdentifier">The identifier by which the authorization server should recognize this client.</param> + /// <param name="request">The outbound message to apply authentication information to.</param> + public override void ApplyClientCredential(string clientIdentifier, AuthenticatedClientRequestBase request) { + request.ClientSecret = this.secret; + } + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/UserAgentClient.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/UserAgentClient.cs index 9834985..edde2a9 100644 --- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/UserAgentClient.cs +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/UserAgentClient.cs @@ -27,7 +27,7 @@ namespace DotNetOpenAuth.OAuth2 { /// <param name="clientIdentifier">The client identifier.</param> /// <param name="clientSecret">The client secret.</param> public UserAgentClient(AuthorizationServerDescription authorizationServer, string clientIdentifier = null, string clientSecret = null) - : base(authorizationServer, clientIdentifier, clientSecret) { + : this(authorizationServer, clientIdentifier, DefaultSecretApplicator(clientSecret)) { } /// <summary> @@ -38,12 +38,39 @@ namespace DotNetOpenAuth.OAuth2 { /// <param name="clientIdentifier">The client identifier.</param> /// <param name="clientSecret">The client secret.</param> public UserAgentClient(Uri authorizationEndpoint, Uri tokenEndpoint, string clientIdentifier = null, string clientSecret = null) - : this(new AuthorizationServerDescription { AuthorizationEndpoint = authorizationEndpoint, TokenEndpoint = tokenEndpoint }, clientIdentifier, clientSecret) { + : this(authorizationEndpoint, tokenEndpoint, clientIdentifier, DefaultSecretApplicator(clientSecret)) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="UserAgentClient"/> class. + /// </summary> + /// <param name="authorizationEndpoint">The authorization endpoint.</param> + /// <param name="tokenEndpoint">The token endpoint.</param> + /// <param name="clientIdentifier">The client identifier.</param> + /// <param name="clientCredentialApplicator"> + /// The tool to use to apply client credentials to authenticated requests to the Authorization Server. + /// May be <c>null</c> for clients with no secret or other means of authentication. + /// </param> + public UserAgentClient(Uri authorizationEndpoint, Uri tokenEndpoint, string clientIdentifier, ClientCredentialApplicator clientCredentialApplicator) + : this(new AuthorizationServerDescription { AuthorizationEndpoint = authorizationEndpoint, TokenEndpoint = tokenEndpoint }, clientIdentifier, clientCredentialApplicator) { Requires.NotNull(authorizationEndpoint, "authorizationEndpoint"); Requires.NotNull(tokenEndpoint, "tokenEndpoint"); } /// <summary> + /// Initializes a new instance of the <see cref="UserAgentClient"/> class. + /// </summary> + /// <param name="authorizationServer">The token issuer.</param> + /// <param name="clientIdentifier">The client identifier.</param> + /// <param name="clientCredentialApplicator"> + /// The tool to use to apply client credentials to authenticated requests to the Authorization Server. + /// May be <c>null</c> for clients with no secret or other means of authentication. + /// </param> + public UserAgentClient(AuthorizationServerDescription authorizationServer, string clientIdentifier, ClientCredentialApplicator clientCredentialApplicator) + : base(authorizationServer, clientIdentifier, clientCredentialApplicator) { + } + + /// <summary> /// Generates a URL that the user's browser can be directed to in order to authorize /// this client to access protected data at some resource server. /// </summary> diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs index 5a86bef..c19757f 100644 --- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs @@ -26,7 +26,20 @@ namespace DotNetOpenAuth.OAuth2 { /// <param name="clientIdentifier">The client identifier.</param> /// <param name="clientSecret">The client secret.</param> public WebServerClient(AuthorizationServerDescription authorizationServer, string clientIdentifier = null, string clientSecret = null) - : base(authorizationServer, clientIdentifier, clientSecret) { + : this(authorizationServer, clientIdentifier, DefaultSecretApplicator(clientSecret)) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="WebServerClient"/> class. + /// </summary> + /// <param name="authorizationServer">The authorization server.</param> + /// <param name="clientIdentifier">The client identifier.</param> + /// <param name="clientCredentialApplicator"> + /// The tool to use to apply client credentials to authenticated requests to the Authorization Server. + /// May be <c>null</c> for clients with no secret or other means of authentication. + /// </param> + public WebServerClient(AuthorizationServerDescription authorizationServer, string clientIdentifier, ClientCredentialApplicator clientCredentialApplicator) + : base(authorizationServer, clientIdentifier, clientCredentialApplicator) { } /// <summary> @@ -106,7 +119,7 @@ namespace DotNetOpenAuth.OAuth2 { /// <returns>The authorization state that contains the details of the authorization.</returns> public IAuthorizationState ProcessUserAuthorization(HttpRequestBase request = null) { Requires.ValidState(!string.IsNullOrEmpty(this.ClientIdentifier), ClientStrings.RequiredPropertyNotYetPreset, "ClientIdentifier"); - Requires.ValidState(!string.IsNullOrEmpty(this.ClientSecret), ClientStrings.RequiredPropertyNotYetPreset, "ClientSecret"); + Requires.ValidState(this.ClientCredentialApplicator != null, ClientStrings.RequiredPropertyNotYetPreset, "ClientCredentialApplicator"); if (request == null) { request = this.Channel.GetRequestFromContext(); |