diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2010-05-29 22:30:01 -0700 |
---|---|---|
committer | Andrew Arnott <andrewarnott@gmail.com> | 2010-05-29 22:30:01 -0700 |
commit | da15c78d8297d41018094621bbf73620a55027e6 (patch) | |
tree | 747358e09b6be287f89c9ab601712a196f52196e /src | |
parent | dc7b69cf46ab08b690454bb454a286a339f78d4d (diff) | |
download | DotNetOpenAuth-da15c78d8297d41018094621bbf73620a55027e6.zip DotNetOpenAuth-da15c78d8297d41018094621bbf73620a55027e6.tar.gz DotNetOpenAuth-da15c78d8297d41018094621bbf73620a55027e6.tar.bz2 |
Tons of work toward meaningfully creating and processing verification codes and preparing to issue access tokens.
Diffstat (limited to 'src')
18 files changed, 592 insertions, 22 deletions
diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj index 9d1e7e8..6dcc742 100644 --- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj +++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj @@ -308,9 +308,12 @@ http://opensource.org/licenses/ms-pl.html <Compile Include="Messaging\StandardMessageFactory.cs" /> <Compile Include="OAuthWrap\AuthorizationServerBase.cs" /> <Compile Include="OAuthWrap\AuthorizationState.cs" /> + <Compile Include="OAuthWrap\ChannelElements\IAccessTokenRequest.cs" /> <Compile Include="OAuthWrap\ChannelElements\OAuthWrapResourceServerChannel.cs" /> <Compile Include="Messaging\StandardMessageFactoryChannel.cs" /> <Compile Include="OAuthWrap\ChannelElements\TimestampEncoder.cs" /> + <Compile Include="OAuthWrap\ChannelElements\VerificationCode.cs" /> + <Compile Include="OAuthWrap\ChannelElements\WebAppAccessTokenRequestVerifier.cs" /> <Compile Include="OAuthWrap\IAccessTokenAnalyzer.cs" /> <Compile Include="OAuthWrap\IAuthorizationServer.cs" /> <Compile Include="OAuthWrap\IAuthorizationState.cs" /> @@ -320,6 +323,7 @@ http://opensource.org/licenses/ms-pl.html <Compile Include="OAuthWrap\Messages\ClientCredentials\ClientCredentialsRequest.cs" /> <Compile Include="OAuthWrap\Messages\Assertion\AssertionSuccessResponse.cs" /> <Compile Include="OAuthWrap\Messages\IMessageWithClientState.cs" /> + <Compile Include="OAuthWrap\Messages\IOAuthDirectResponseFormat.cs" /> <Compile Include="OAuthWrap\Messages\RefreshAccessTokenRequest.cs" /> <Compile Include="OAuthWrap\Messages\Device\RichAppAccessTokenRequest.cs" /> <Compile Include="OAuthWrap\Messages\Device\RichAppRequest.cs" /> @@ -334,6 +338,8 @@ http://opensource.org/licenses/ms-pl.html <Compile Include="OAuthWrap\Messages\UserAgent\UserAgentSuccessResponse.cs" /> <Compile Include="OAuthWrap\Messages\UsernameAndPassword\UserNamePasswordCaptchaResponse.cs" /> <Compile Include="OAuthWrap\Messages\UsernameAndPassword\UserNamePasswordVerificationResponse.cs" /> + <Compile Include="OAuthWrap\Messages\WebServer\ResponseFormat.cs" /> + <Compile Include="OAuthWrap\Messages\WebServer\ResponseFormatEncoder.cs" /> <Compile Include="OAuthWrap\ResourceServer.cs" /> <Compile Include="OAuthWrap\WebAppAuthorizationServer.cs" /> <Compile Include="OAuthWrap\WrapUtilities.cs" /> diff --git a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs index 44fffe5..90710d0 100644 --- a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs +++ b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs @@ -392,6 +392,64 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Computes the hash of a string. + /// </summary> + /// <param name="algorithm">The hash algorithm to use.</param> + /// <param name="value">The value to hash.</param> + /// <param name="encoding">The encoding to use when converting the string to a byte array.</param> + /// <returns>A base64 encoded string.</returns> + internal static string ComputeHash(this HashAlgorithm algorithm, string value, Encoding encoding = null) + { + Contract.Requires<ArgumentNullException>(algorithm != null, "algorithm"); + Contract.Requires<ArgumentNullException>(value != null, "value"); + Contract.Ensures(Contract.Result<string>() != null); + + encoding = encoding ?? Encoding.UTF8; + byte[] bytesToHash = encoding.GetBytes(value); + byte[] hash = algorithm.ComputeHash(bytesToHash); + string base64Hash = Convert.ToBase64String(hash); + return base64Hash; + } + + /// <summary> + /// Computes the hash of a sequence of key=value pairs. + /// </summary> + /// <param name="algorithm">The hash algorithm to use.</param> + /// <param name="data">The data to hash.</param> + /// <param name="encoding">The encoding to use when converting the string to a byte array.</param> + /// <returns>A base64 encoded string.</returns> + internal static string ComputeHash(this HashAlgorithm algorithm, IDictionary<string, string> data, Encoding encoding = null) + { + Contract.Requires<ArgumentNullException>(algorithm != null, "algorithm"); + Contract.Requires<ArgumentNullException>(data != null, "data"); + Contract.Ensures(Contract.Result<string>() != null); + + // Assemble the dictionary to sign, taking care to remove the signature itself + // in order to accurately reproduce the original signature (which of course didn't include + // the signature). + // Also we need to sort the dictionary's keys so that we sign in the same order as we did + // the last time. + var sortedData = new SortedDictionary<string, string>(data, StringComparer.OrdinalIgnoreCase); + return ComputeHash(algorithm, sortedData, encoding); + } + + /// <summary> + /// Computes the hash of a sequence of key=value pairs. + /// </summary> + /// <param name="algorithm">The hash algorithm to use.</param> + /// <param name="sortedData">The data to hash.</param> + /// <param name="encoding">The encoding to use when converting the string to a byte array.</param> + /// <returns>A base64 encoded string.</returns> + internal static string ComputeHash(this HashAlgorithm algorithm, IEnumerable<KeyValuePair<string, string>> sortedData, Encoding encoding = null) + { + Contract.Requires<ArgumentNullException>(algorithm != null, "algorithm"); + Contract.Requires<ArgumentNullException>(sortedData != null, "sortedData"); + Contract.Ensures(Contract.Result<string>() != null); + + return ComputeHash(algorithm, CreateQueryString(sortedData), encoding); + } + +/// <summary> /// Adds a set of HTTP headers to an <see cref="HttpResponse"/> instance, /// taking care to set some headers to the appropriate properties of /// <see cref="HttpResponse" /> diff --git a/src/DotNetOpenAuth/OAuthWrap/AuthorizationServerBase.cs b/src/DotNetOpenAuth/OAuthWrap/AuthorizationServerBase.cs index 396ff87..1875bd1 100644 --- a/src/DotNetOpenAuth/OAuthWrap/AuthorizationServerBase.cs +++ b/src/DotNetOpenAuth/OAuthWrap/AuthorizationServerBase.cs @@ -4,15 +4,15 @@ // </copyright> //----------------------------------------------------------------------- -using System.Diagnostics.Contracts; -using DotNetOpenAuth.OAuth.ChannelElements; - namespace DotNetOpenAuth.OAuthWrap { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.Linq; using System.Text; + using ChannelElements; using DotNetOpenAuth.Messaging; + using OAuth.ChannelElements; public abstract class AuthorizationServerBase { protected AuthorizationServerBase(IAuthorizationServer authorizationServer) { @@ -22,6 +22,10 @@ namespace DotNetOpenAuth.OAuthWrap { public Channel Channel { get; set; } + internal OAuthWrapAuthorizationServerChannel OAuthChannel { + get { return (OAuthWrapAuthorizationServerChannel)this.Channel; } + } + public IAuthorizationServer AuthorizationServer { get; set; } protected IConsumerDescription GetClient(string clientIdentifier) { diff --git a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/IAccessTokenRequest.cs b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/IAccessTokenRequest.cs new file mode 100644 index 0000000..34fa329 --- /dev/null +++ b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/IAccessTokenRequest.cs @@ -0,0 +1,20 @@ +//----------------------------------------------------------------------- +// <copyright file="IAccessTokenRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuthWrap.ChannelElements { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using Messages; + using Messaging; + + internal interface IAccessTokenRequest : IDirectedProtocolMessage { + string ClientIdentifier { get; } + + string Scope { get; } + } +} diff --git a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapAuthorizationServerChannel.cs b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapAuthorizationServerChannel.cs index 876e1d8..063d941 100644 --- a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapAuthorizationServerChannel.cs +++ b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapAuthorizationServerChannel.cs @@ -4,6 +4,9 @@ // </copyright> //----------------------------------------------------------------------- +using DotNetOpenAuth.Messaging.Bindings; +using DotNetOpenAuth.OAuthWrap.Messages; + namespace DotNetOpenAuth.OAuthWrap.ChannelElements { using System; using System.Collections.Generic; @@ -19,6 +22,8 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { /// The channel for the OAuth WRAP protocol. /// </summary> internal class OAuthWrapAuthorizationServerChannel : StandardMessageFactoryChannel { + public IAuthorizationServer AuthorizationServer { get; set; } + private static readonly Type[] MessageTypes = new Type[] { typeof(Messages.RefreshAccessTokenRequest), typeof(Messages.AccessTokenSuccessResponse), @@ -49,10 +54,23 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { private static readonly Version[] Versions = Protocol.AllVersions.Select(v => v.Version).ToArray(); /// <summary> - /// Initializes a new instance of the <see cref="OAuthWrapChannel"/> class. + /// Initializes a new instance of the <see cref="OAuthWrapAuthorizationServerChannel"/> class. /// </summary> - protected internal OAuthWrapAuthorizationServerChannel() - : base(MessageTypes, Versions) { + protected internal OAuthWrapAuthorizationServerChannel(IAuthorizationServer authorizationServer) + : base(MessageTypes, Versions, InitializeBindingElements(authorizationServer)) { + Contract.Requires<ArgumentNullException>(authorizationServer != null, "authorizationServer"); + + this.AuthorizationServer = authorizationServer; + } + + public virtual AccessTokenSuccessResponse PrepareAccessToken(IAccessTokenRequest request) { + Contract.Requires<ArgumentNullException>(request != null, "request"); + + var response = new AccessTokenSuccessResponse(request) { + // TODO: code here to initialize the response + }; + + return response; } /// <summary> @@ -111,7 +129,42 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { /// This method implements spec OAuth V1.0 section 5.3. /// </remarks> protected override OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response) { + var directResponse = (IDirectResponseProtocolMessage)response; + var formatSpecifyingRequest = directResponse.OriginatingRequest as IOAuthDirectResponseFormat; + if (formatSpecifyingRequest != null) + { + ResponseFormat format = formatSpecifyingRequest.Format; + switch (format) + { + case ResponseFormat.Xml: + throw new NotImplementedException(); + case ResponseFormat.Form: + throw new NotImplementedException(); + case ResponseFormat.Json: + throw new NotImplementedException(); + default: + throw ErrorUtilities.ThrowInternal("Unrecognized value of ResponseFormat enum: " + format); + } + } + throw new NotImplementedException(); } + + /// <summary> + /// Initializes the binding elements for the OAuth channel. + /// </summary> + /// <param name="authorizationServer">The authorization server.</param> + /// <returns> + /// An array of binding elements used to initialize the channel. + /// </returns> + private static IChannelBindingElement[] InitializeBindingElements(IAuthorizationServer authorizationServer) { + Contract.Requires<ArgumentNullException>(authorizationServer != null, "authorizationServer"); + + var bindingElements = new List<IChannelBindingElement> { + new WebAppAccessTokenRequestVerifier(), + }; + + return bindingElements.ToArray(); + } } } diff --git a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/TimestampEncoder.cs b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/TimestampEncoder.cs index c23488c..32eac62 100644 --- a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/TimestampEncoder.cs +++ b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/TimestampEncoder.cs @@ -29,7 +29,11 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { /// The <paramref name="value"/> in string form, ready for message transport. /// </returns> public string Encode(object value) { - var timestamp = (DateTime) value; + if (value == null) { + return null; + } + + var timestamp = (DateTime)value; TimeSpan secondsSinceEpoch = timestamp - Epoch; return secondsSinceEpoch.TotalSeconds.ToString(CultureInfo.InvariantCulture); } @@ -43,6 +47,10 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { /// </returns> /// <exception cref="FormatException">Thrown when the string value given cannot be decoded into the required object type.</exception> public object Decode(string value) { + if (value == null) { + return null; + } + var secondsSinceEpoch = Convert.ToInt32(value, CultureInfo.InvariantCulture); return Epoch.AddSeconds(secondsSinceEpoch); } diff --git a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/VerificationCode.cs b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/VerificationCode.cs new file mode 100644 index 0000000..3a46517 --- /dev/null +++ b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/VerificationCode.cs @@ -0,0 +1,131 @@ +using System.Diagnostics.Contracts; +using System.Security.Cryptography; +using System.Web; +using DotNetOpenAuth.Messaging; + +namespace DotNetOpenAuth.OAuthWrap.ChannelElements { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.OAuthWrap.Messages; + + internal class VerificationCode : MessageBase, IMessageWithEvents { + private HashAlgorithm hasher; + + private const int NonceLength = 6; + + /// <summary> + /// Initializes a new instance of the <see cref="VerificationCode"/> class. + /// </summary> + /// <param name="channel">The channel.</param> + /// <param name="callback">The callback.</param> + internal VerificationCode(OAuthWrapAuthorizationServerChannel channel, Uri callback, string scope) + : this(channel) { + Contract.Requires<ArgumentNullException>(channel != null, "channel"); + Contract.Requires<ArgumentNullException>(callback != null, "callback"); + + this.CallbackHash = this.CalculateCallbackHash(callback); + this.Scope = scope; + this.CreationDateUtc = DateTime.UtcNow; + this.Nonce = Convert.ToBase64String(MessagingUtilities.GetNonCryptoRandomData(NonceLength)); + } + + /// <summary> + /// Initializes a new instance of the <see cref="VerificationCode"/> class. + /// </summary> + /// <param name="channel">The channel.</param> + private VerificationCode(OAuthWrapAuthorizationServerChannel channel) + : base(Protocol.Default.Version) { + Contract.Requires<ArgumentNullException>(channel != null, "channel"); + this.Channel = channel; + this.hasher = new HMACSHA256(this.Channel.AuthorizationServer.Secret); + } + + /// <summary> + /// Gets or sets the channel. + /// </summary> + public OAuthWrapAuthorizationServerChannel Channel { get; set; } + + [MessagePart("cb")] + private string CallbackHash { get; set; } + + [MessagePart("scope")] + internal string Scope { get; set; } + + [MessagePart("nonce")] + internal string Nonce { get; set; } + + [MessagePart("timestamp", Encoder = typeof(TimestampEncoder))] + internal DateTime CreationDateUtc { get; set; } + + [MessagePart("sig")] + private string Signature { get; set; } + + /// <summary> + /// Called when the message is about to be transmitted, + /// before it passes through the channel binding elements. + /// </summary> + void IMessageWithEvents.OnSending() { + this.Signature = CalculateSignature(); + } + + /// <summary> + /// Called when the message has been received, + /// after it passes through the channel binding elements. + /// </summary> + void IMessageWithEvents.OnReceiving() { + // Verify that the verification code was issued by this authorization server. + ErrorUtilities.VerifyProtocol(string.Equals(this.Signature, this.CalculateSignature(), StringComparison.Ordinal), Protocol.bad_verification_code); + } + + internal static VerificationCode Decode(OAuthWrapAuthorizationServerChannel channel, string value) { + Contract.Requires<ArgumentNullException>(channel != null, "channel"); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(value)); + Contract.Ensures(Contract.Result<VerificationCode>() != null); + + // Construct a new instance of this type. + VerificationCode self = new VerificationCode(channel); + var fields = channel.MessageDescriptions.GetAccessor(self); + + // Deserialize into this newly created instance. + var nvc = HttpUtility.ParseQueryString(value); + foreach (string key in nvc) { + fields[key] = nvc[key]; + } + + ((IMessageWithEvents)self).OnReceiving(); + + return self; + } + + internal string Encode() { + Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>())); + + ((IMessageWithEvents)this).OnSending(); + + var fields = this.Channel.MessageDescriptions.GetAccessor(this); + return MessagingUtilities.CreateQueryString(fields); + } + + internal void VerifyCallback(Uri callback) { + ErrorUtilities.VerifyProtocol(string.Equals(this.CallbackHash, this.CalculateCallbackHash(callback), StringComparison.Ordinal), Protocol.redirect_uri_mismatch); + } + + private string CalculateCallbackHash(Uri callback) { + return this.hasher.ComputeHash(callback.AbsoluteUri); + } + + /// <summary> + /// Calculates the signature for the data in this verification code. + /// </summary> + /// <returns>The calculated signature.</returns> + private string CalculateSignature() { + // Sign the data, being sure to avoid any impact of the signature field itself. + var fields = this.Channel.MessageDescriptions.GetAccessor(this); + var fieldsCopy = fields.ToDictionary(); + fieldsCopy.Remove("sig"); + return this.hasher.ComputeHash(fieldsCopy); + } + } +} diff --git a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/WebAppAccessTokenRequestVerifier.cs b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/WebAppAccessTokenRequestVerifier.cs new file mode 100644 index 0000000..645f55d --- /dev/null +++ b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/WebAppAccessTokenRequestVerifier.cs @@ -0,0 +1,135 @@ +//----------------------------------------------------------------------- +// <copyright file="WebAppAccessTokenRequestVerifier.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +using DotNetOpenAuth.Messaging.Bindings; + +namespace DotNetOpenAuth.OAuthWrap.ChannelElements { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using Messages; + using Messaging; + + internal class WebAppAccessTokenRequestVerifier : IChannelBindingElement { + private const string VerificationCodeContext = "{VerificationCode}"; + + /// <summary> + /// Initializes a new instance of the <see cref="WebAppAccessTokenRequestVerifier"/> class. + /// </summary> + internal WebAppAccessTokenRequestVerifier() { + } + + /// <summary> + /// Gets or sets the channel that this binding element belongs to. + /// </summary> + /// <remarks> + /// This property is set by the channel when it is first constructed. + /// </remarks> + public Channel Channel { get; set; } + + /// <summary> + /// Gets the protection commonly offered (if any) by this binding element. + /// </summary> + /// <value>Always <c>MessageProtections.None</c></value> + /// <remarks> + /// This value is used to assist in sorting binding elements in the channel stack. + /// </remarks> + public MessageProtections Protection { + get { return MessageProtections.None; } + } + + protected OAuthWrapAuthorizationServerChannel OAuthChannel { + get { return (OAuthWrapAuthorizationServerChannel)this.Channel; } + } + + /// <summary> + /// Gets the maximum message age from the standard expiration binding element. + /// </summary> + private static TimeSpan MaximumMessageAge { + get { return StandardExpirationBindingElement.MaximumMessageAge; } + } + + /// <summary> + /// Gets the authorization server hosting this channel. + /// </summary> + /// <value>The authorization server.</value> + private IAuthorizationServer AuthorizationServer { + get { return this.OAuthChannel.AuthorizationServer; } + } + + /// <summary> + /// Prepares a message for sending based on the rules of this channel binding element. + /// </summary> + /// <param name="message">The message to prepare for sending.</param> + /// <returns> + /// The protections (if any) that this binding element applied to the message. + /// Null if this binding element did not even apply to this binding element. + /// </returns> + /// <remarks> + /// Implementations that provide message protection must honor the + /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable. + /// </remarks> + public MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { + var response = message as WebAppSuccessResponse; + if (response != null) { + var directResponse = response as IDirectResponseProtocolMessage; + var request = directResponse.OriginatingRequest as WebAppRequest; + + var code = new VerificationCode(this.OAuthChannel, request.Callback, request.Scope); + response.VerificationCode = code.Encode(); + + return MessageProtections.None; + } + + return null; + } + + /// <summary> + /// Performs any transformation on an incoming message that may be necessary and/or + /// validates an incoming message based on the rules of this channel binding element. + /// </summary> + /// <param name="message">The incoming message to process.</param> + /// <returns> + /// The protections (if any) that this binding element applied to the message. + /// Null if this binding element did not even apply to this binding element. + /// </returns> + /// <exception cref="ProtocolException"> + /// Thrown when the binding element rules indicate that this message is invalid and should + /// NOT be processed. + /// </exception> + /// <remarks> + /// Implementations that provide message protection must honor the + /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable. + /// </remarks> + public MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { + var request = message as WebAppAccessTokenRequest; + if (request != null) { + var client = this.AuthorizationServer.GetClient(request.ClientIdentifier); + ErrorUtilities.VerifyProtocol(string.Equals(client.Secret, request.ClientSecret, StringComparison.Ordinal), Protocol.incorrect_client_credentials); + + var verificationCode = VerificationCode.Decode(this.OAuthChannel, request.VerificationCode); + verificationCode.VerifyCallback(request.Callback); + + // Has this verification code expired? + DateTime expirationDate = verificationCode.CreationDateUtc + MaximumMessageAge; + if (expirationDate < DateTime.UtcNow) { + throw new ExpiredMessageException(expirationDate, message); + } + + // Has this verification code already been used to obtain an access/refresh token? + if (!this.AuthorizationServer.VerificationCodeNonceStore.StoreNonce(VerificationCodeContext, verificationCode.Nonce, verificationCode.CreationDateUtc)) { + Logger.OpenId.ErrorFormat("Replayed nonce detected ({0} {1}). Rejecting message.", verificationCode.Nonce, verificationCode.CreationDateUtc); + throw new ReplayedMessageException(message); + } + + return MessageProtections.None; + } + + return null; + } + } +} diff --git a/src/DotNetOpenAuth/OAuthWrap/ClientBase.cs b/src/DotNetOpenAuth/OAuthWrap/ClientBase.cs index ec3dbc7..bb308d9 100644 --- a/src/DotNetOpenAuth/OAuthWrap/ClientBase.cs +++ b/src/DotNetOpenAuth/OAuthWrap/ClientBase.cs @@ -26,7 +26,7 @@ namespace DotNetOpenAuth.OAuthWrap { protected ClientBase(AuthorizationServerDescription authorizationServer) { Contract.Requires<ArgumentNullException>(authorizationServer != null); this.AuthorizationServer = authorizationServer; - this.Channel = new OAuthWrapAuthorizationServerChannel(); + this.Channel = new OAuthWrapAuthorizationServerChannel(authorizationServer); } /// <summary> diff --git a/src/DotNetOpenAuth/OAuthWrap/IAuthorizationServer.cs b/src/DotNetOpenAuth/OAuthWrap/IAuthorizationServer.cs index 5be4c55..c92ecc7 100644 --- a/src/DotNetOpenAuth/OAuthWrap/IAuthorizationServer.cs +++ b/src/DotNetOpenAuth/OAuthWrap/IAuthorizationServer.cs @@ -4,6 +4,8 @@ // </copyright> //----------------------------------------------------------------------- +using DotNetOpenAuth.Messaging.Bindings; + namespace DotNetOpenAuth.OAuthWrap { using System; using System.Collections.Generic; @@ -15,6 +17,10 @@ namespace DotNetOpenAuth.OAuthWrap { [ContractClass(typeof(IAuthorizationServerContract))] public interface IAuthorizationServer { IConsumerDescription GetClient(string clientIdentifier); + + byte[] Secret { get; } + + INonceStore VerificationCodeNonceStore { get; } } [ContractClassFor(typeof(IAuthorizationServer))] @@ -27,6 +33,20 @@ namespace DotNetOpenAuth.OAuthWrap { Contract.Ensures(Contract.Result<IConsumerDescription>() != null); throw new NotImplementedException(); } + + byte[] IAuthorizationServer.Secret { + get { + Contract.Ensures(Contract.Result<byte[]>() != null); + throw new NotImplementedException(); + } + } + + INonceStore IAuthorizationServer.VerificationCodeNonceStore { + get { + Contract.Ensures(Contract.Result<INonceStore>() != null); + throw new NotImplementedException(); + } + } } } diff --git a/src/DotNetOpenAuth/OAuthWrap/Messages/AccessTokenFailedResponse.cs b/src/DotNetOpenAuth/OAuthWrap/Messages/AccessTokenFailedResponse.cs index c487ebc..4a44aa3 100644 --- a/src/DotNetOpenAuth/OAuthWrap/Messages/AccessTokenFailedResponse.cs +++ b/src/DotNetOpenAuth/OAuthWrap/Messages/AccessTokenFailedResponse.cs @@ -5,7 +5,8 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.OAuthWrap.Messages { - using DotNetOpenAuth.Messaging; + using ChannelElements; + using Messaging; /// <summary> /// A response from the Authorization Server to the Client to indicate that a @@ -20,7 +21,7 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { /// Initializes a new instance of the <see cref="AccessTokenFailedResponse"/> class. /// </summary> /// <param name="request">The request.</param> - internal AccessTokenFailedResponse(IDirectedProtocolMessage request) + internal AccessTokenFailedResponse(IAccessTokenRequest request) : base(request) { } diff --git a/src/DotNetOpenAuth/OAuthWrap/Messages/AccessTokenSuccessResponse.cs b/src/DotNetOpenAuth/OAuthWrap/Messages/AccessTokenSuccessResponse.cs index 3c2714a..2f9b770 100644 --- a/src/DotNetOpenAuth/OAuthWrap/Messages/AccessTokenSuccessResponse.cs +++ b/src/DotNetOpenAuth/OAuthWrap/Messages/AccessTokenSuccessResponse.cs @@ -4,6 +4,8 @@ // </copyright> //----------------------------------------------------------------------- +using DotNetOpenAuth.OAuthWrap.ChannelElements; + namespace DotNetOpenAuth.OAuthWrap.Messages { using System; using System.Net; @@ -21,7 +23,7 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { /// Initializes a new instance of the <see cref="AccessTokenSuccessResponse"/> class. /// </summary> /// <param name="request">The request.</param> - internal AccessTokenSuccessResponse(IDirectedProtocolMessage request) + internal AccessTokenSuccessResponse(IAccessTokenRequest request) : base(request) { } diff --git a/src/DotNetOpenAuth/OAuthWrap/Messages/IOAuthDirectResponseFormat.cs b/src/DotNetOpenAuth/OAuthWrap/Messages/IOAuthDirectResponseFormat.cs new file mode 100644 index 0000000..fa7a919 --- /dev/null +++ b/src/DotNetOpenAuth/OAuthWrap/Messages/IOAuthDirectResponseFormat.cs @@ -0,0 +1,16 @@ +//----------------------------------------------------------------------- +// <copyright file="IOAuthDirectResponseFormat.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuthWrap.Messages { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + internal interface IOAuthDirectResponseFormat { + ResponseFormat Format { get; } + } +} diff --git a/src/DotNetOpenAuth/OAuthWrap/Messages/WebServer/ResponseFormat.cs b/src/DotNetOpenAuth/OAuthWrap/Messages/WebServer/ResponseFormat.cs new file mode 100644 index 0000000..720c62b --- /dev/null +++ b/src/DotNetOpenAuth/OAuthWrap/Messages/WebServer/ResponseFormat.cs @@ -0,0 +1,18 @@ +//----------------------------------------------------------------------- +// <copyright file="ResponseFormat.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuthWrap.Messages { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + public enum ResponseFormat { + Json, + Xml, + Form, + } +} diff --git a/src/DotNetOpenAuth/OAuthWrap/Messages/WebServer/ResponseFormatEncoder.cs b/src/DotNetOpenAuth/OAuthWrap/Messages/WebServer/ResponseFormatEncoder.cs new file mode 100644 index 0000000..f806b4e --- /dev/null +++ b/src/DotNetOpenAuth/OAuthWrap/Messages/WebServer/ResponseFormatEncoder.cs @@ -0,0 +1,71 @@ +//----------------------------------------------------------------------- +// <copyright file="ResponseFormatEncoder.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuthWrap.Messages.WebServer { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Reflection; + + /// <summary> + /// Provides encoding/decoding of the json/xml/form enum type. + /// </summary> + public class ResponseFormatEncoder : IMessagePartEncoder { + /// <summary> + /// Initializes a new instance of the <see cref="ResponseFormatEncoder"/> class. + /// </summary> + public ResponseFormatEncoder() { + } + + /// <summary> + /// Encodes the specified value. + /// </summary> + /// <param name="value">The value. Guaranteed to never be null.</param> + /// <returns> + /// The <paramref name="value"/> in string form, ready for message transport. + /// </returns> + public string Encode(object value) { + if (value == null) { + return null; + } + + var format = (ResponseFormat)value; + switch (format) { + case ResponseFormat.Xml: + return Protocol.ResponseFormats.Xml; + case ResponseFormat.Json: + return Protocol.ResponseFormats.Json; + case ResponseFormat.Form: + return Protocol.ResponseFormats.Form; + default: + throw new ArgumentOutOfRangeException("value"); + } + } + + /// <summary> + /// Decodes the specified value. + /// </summary> + /// <param name="value">The string value carried by the transport. Guaranteed to never be null, although it may be empty.</param> + /// <returns> + /// The deserialized form of the given string. + /// </returns> + /// <exception cref="FormatException">Thrown when the string value given cannot be decoded into the required object type.</exception> + public object Decode(string value) { + switch (value) { + case Protocol.ResponseFormats.Xml: + return ResponseFormat.Xml; + case Protocol.ResponseFormats.Json: + return ResponseFormat.Json; + case Protocol.ResponseFormats.Form: + return ResponseFormat.Form; + default: + throw new ArgumentOutOfRangeException("value"); + } + } + } +} diff --git a/src/DotNetOpenAuth/OAuthWrap/Messages/WebServer/WebAppAccessTokenRequest.cs b/src/DotNetOpenAuth/OAuthWrap/Messages/WebServer/WebAppAccessTokenRequest.cs index de3ef40..fe6f43a 100644 --- a/src/DotNetOpenAuth/OAuthWrap/Messages/WebServer/WebAppAccessTokenRequest.cs +++ b/src/DotNetOpenAuth/OAuthWrap/Messages/WebServer/WebAppAccessTokenRequest.cs @@ -4,6 +4,8 @@ // </copyright> //----------------------------------------------------------------------- +using DotNetOpenAuth.OAuthWrap.Messages.WebServer; + namespace DotNetOpenAuth.OAuthWrap.Messages { using System; using System.Diagnostics.Contracts; @@ -17,7 +19,7 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { /// <remarks> /// Used by the Web App (and Rich App?) profiles. /// </remarks> - internal class WebAppAccessTokenRequest : MessageBase { + internal class WebAppAccessTokenRequest : MessageBase, IAccessTokenRequest, IOAuthDirectResponseFormat { /// <summary> /// The type of message. /// </summary> @@ -50,7 +52,7 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { /// </summary> /// <value>The client identifier.</value> [MessagePart(Protocol.client_id, IsRequired = true, AllowEmpty = false)] - internal string ClientIdentifier { get; set; } + public string ClientIdentifier { get; set; } /// <summary> /// Gets or sets the client secret. @@ -87,7 +89,16 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { /// OPTIONAL. The access token secret type as described by Section 5.3 (Cryptographic Tokens Requests). If omitted, the authorization server will issue a bearer token (an access token without a matching secret) as described by Section 5.2 (Bearer Token Requests). /// </remarks> [MessagePart(Protocol.secret_type, IsRequired = false, AllowEmpty = false)] - internal string SecretType { get; set; } + internal string SecretType { get; set; } + + public string Scope { get; internal set; } + + ResponseFormat IOAuthDirectResponseFormat.Format { + get { return this.Format.HasValue ? this.Format.Value : ResponseFormat.Json; } + } + + [MessagePart(Protocol.format, Encoder = typeof(ResponseFormatEncoder))] + private ResponseFormat? Format { get; set; } /// <summary> /// Checks the message state for conformity to the protocol specification diff --git a/src/DotNetOpenAuth/OAuthWrap/Protocol.cs b/src/DotNetOpenAuth/OAuthWrap/Protocol.cs index 8dc10ee..4d71ef7 100644 --- a/src/DotNetOpenAuth/OAuthWrap/Protocol.cs +++ b/src/DotNetOpenAuth/OAuthWrap/Protocol.cs @@ -44,6 +44,21 @@ namespace DotNetOpenAuth.OAuthWrap { internal const string state = "state"; /// <summary> + /// The "redirect_uri_mismatch" string. + /// </summary> + internal const string redirect_uri_mismatch = "redirect_uri_mismatch"; + + /// <summary> + /// The "bad_verification_code" string. + /// </summary> + internal const string bad_verification_code = "bad_verification_code"; + + /// <summary> + /// The "incorrect_client_credentials" string. + /// </summary> + internal const string incorrect_client_credentials = "incorrect_client_credentials"; + + /// <summary> /// The "redirect_uri" string. /// </summary> internal const string redirect_uri = "redirect_uri"; @@ -219,5 +234,12 @@ namespace DotNetOpenAuth.OAuthWrap { default: throw new ArgumentOutOfRangeException("version"); } } + + internal static class ResponseFormats + { + internal const string Json = "json"; + internal const string Xml = "xml"; + internal const string Form = "form"; + } } } diff --git a/src/DotNetOpenAuth/OAuthWrap/WebAppAuthorizationServer.cs b/src/DotNetOpenAuth/OAuthWrap/WebAppAuthorizationServer.cs index 5959f1d..9ba212f 100644 --- a/src/DotNetOpenAuth/OAuthWrap/WebAppAuthorizationServer.cs +++ b/src/DotNetOpenAuth/OAuthWrap/WebAppAuthorizationServer.cs @@ -98,10 +98,7 @@ namespace DotNetOpenAuth.OAuthWrap { } var client = GetClient(authorizationRequest.ClientIdentifier); - var response = new WebAppSuccessResponse(callback, authorizationRequest) { - VerificationCode = OAuth.ServiceProvider.CreateVerificationCode(client.VerificationCodeFormat, client.VerificationCodeLength), - }; - + var response = new WebAppSuccessResponse(callback, authorizationRequest); return response; } @@ -117,10 +114,7 @@ namespace DotNetOpenAuth.OAuthWrap { internal AccessTokenSuccessResponse PrepareAccessTokenResponse(WebAppAccessTokenRequest request) { Contract.Requires<ArgumentNullException>(request != null, "request"); - var response = new AccessTokenSuccessResponse(request) { - // TODO: code here to initialize the response - }; - return response; + return this.OAuthChannel.PrepareAccessToken(request); } protected Uri GetCallback(WebAppRequest authorizationRequest) { |