summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2010-05-29 22:30:01 -0700
committerAndrew Arnott <andrewarnott@gmail.com>2010-05-29 22:30:01 -0700
commitda15c78d8297d41018094621bbf73620a55027e6 (patch)
tree747358e09b6be287f89c9ab601712a196f52196e /src
parentdc7b69cf46ab08b690454bb454a286a339f78d4d (diff)
downloadDotNetOpenAuth-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')
-rw-r--r--src/DotNetOpenAuth/DotNetOpenAuth.csproj6
-rw-r--r--src/DotNetOpenAuth/Messaging/MessagingUtilities.cs58
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/AuthorizationServerBase.cs10
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/ChannelElements/IAccessTokenRequest.cs20
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapAuthorizationServerChannel.cs59
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/ChannelElements/TimestampEncoder.cs10
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/ChannelElements/VerificationCode.cs131
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/ChannelElements/WebAppAccessTokenRequestVerifier.cs135
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/ClientBase.cs2
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/IAuthorizationServer.cs20
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/Messages/AccessTokenFailedResponse.cs5
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/Messages/AccessTokenSuccessResponse.cs4
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/Messages/IOAuthDirectResponseFormat.cs16
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/Messages/WebServer/ResponseFormat.cs18
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/Messages/WebServer/ResponseFormatEncoder.cs71
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/Messages/WebServer/WebAppAccessTokenRequest.cs17
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/Protocol.cs22
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/WebAppAuthorizationServer.cs10
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) {