summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2010-05-29 07:15:50 -0700
committerAndrew Arnott <andrewarnott@gmail.com>2010-05-29 07:15:50 -0700
commitb7f504882c34b21db3f98333663f750c2c56d83f (patch)
treeda13b362d580b0dc588dd8713ec1827fcce87196 /src
parent75a1fa8ad5d62e3a8241a2e796c903099c130020 (diff)
downloadDotNetOpenAuth-b7f504882c34b21db3f98333663f750c2c56d83f.zip
DotNetOpenAuth-b7f504882c34b21db3f98333663f750c2c56d83f.tar.gz
DotNetOpenAuth-b7f504882c34b21db3f98333663f750c2c56d83f.tar.bz2
Refactored OAuth 2.0 channels to be separate for authorization servers and resource servers.
Diffstat (limited to 'src')
-rw-r--r--src/DotNetOpenAuth.Test/OAuthWrap/MessageFactoryTests.cs6
-rw-r--r--src/DotNetOpenAuth/DotNetOpenAuth.csproj8
-rw-r--r--src/DotNetOpenAuth/Messaging/Channel.cs2
-rw-r--r--src/DotNetOpenAuth/Messaging/StandardMessageFactoryChannel.cs92
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapAuthorizationServerChannel.cs (renamed from src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapChannel.cs)105
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapResourceServerChannel.cs133
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/ChannelElements/TimestampEncoder.cs50
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/ClientBase.cs2
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/IAccessTokenAnalyzer.cs16
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/Messages/AccessProtectedResourceRequest.cs74
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/Protocol.cs4
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/ResourceServer.cs95
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/WebAppAuthorizationServer.cs6
13 files changed, 512 insertions, 81 deletions
diff --git a/src/DotNetOpenAuth.Test/OAuthWrap/MessageFactoryTests.cs b/src/DotNetOpenAuth.Test/OAuthWrap/MessageFactoryTests.cs
index 414afba..830c489 100644
--- a/src/DotNetOpenAuth.Test/OAuthWrap/MessageFactoryTests.cs
+++ b/src/DotNetOpenAuth.Test/OAuthWrap/MessageFactoryTests.cs
@@ -20,14 +20,14 @@ namespace DotNetOpenAuth.Test.OAuthWrap {
/// </summary>
public class MessageFactoryTests : OAuthWrapTestBase {
private readonly MessageReceivingEndpoint recipient = new MessageReceivingEndpoint("http://who", HttpDeliveryMethods.PostRequest);
- private OAuthWrapChannel channel;
+ private OAuthWrapAuthorizationServerChannel channel;
private IMessageFactory messageFactory;
public override void SetUp() {
base.SetUp();
- this.channel = new OAuthWrapChannel();
- this.messageFactory = OAuthWrapChannel_Accessor.AttachShadow(this.channel).MessageFactory;
+ this.channel = new OAuthWrapAuthorizationServerChannel();
+ this.messageFactory = OAuthWrapAuthorizationServerChannel_Accessor.AttachShadow(this.channel).MessageFactory;
}
#region Refresh Access Token messages
diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj
index dd95d3a..9d1e7e8 100644
--- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj
+++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj
@@ -308,9 +308,14 @@ 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\OAuthWrapResourceServerChannel.cs" />
+ <Compile Include="Messaging\StandardMessageFactoryChannel.cs" />
+ <Compile Include="OAuthWrap\ChannelElements\TimestampEncoder.cs" />
+ <Compile Include="OAuthWrap\IAccessTokenAnalyzer.cs" />
<Compile Include="OAuthWrap\IAuthorizationServer.cs" />
<Compile Include="OAuthWrap\IAuthorizationState.cs" />
<Compile Include="OAuthWrap\IClientTokenManager.cs" />
+ <Compile Include="OAuthWrap\Messages\AccessProtectedResourceRequest.cs" />
<Compile Include="OAuthWrap\Messages\Assertion\AssertionRequest.cs" />
<Compile Include="OAuthWrap\Messages\ClientCredentials\ClientCredentialsRequest.cs" />
<Compile Include="OAuthWrap\Messages\Assertion\AssertionSuccessResponse.cs" />
@@ -329,6 +334,7 @@ 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\ResourceServer.cs" />
<Compile Include="OAuthWrap\WebAppAuthorizationServer.cs" />
<Compile Include="OAuthWrap\WrapUtilities.cs" />
<Compile Include="OAuth\ChannelElements\ICombinedOpenIdProviderTokenManager.cs" />
@@ -617,7 +623,7 @@ http://opensource.org/licenses/ms-pl.html
<Compile Include="Messaging\StandardWebRequestHandler.cs" />
<Compile Include="Messaging\MessageReceivingEndpoint.cs" />
<Compile Include="Reporting.cs" />
- <Compile Include="OAuthWrap\ChannelElements\OAuthWrapChannel.cs" />
+ <Compile Include="OAuthWrap\ChannelElements\OAuthWrapAuthorizationServerChannel.cs" />
<Compile Include="OAuthWrap\ClientBase.cs" />
<Compile Include="OAuthWrap\Messages\MessageBase.cs" />
<Compile Include="OAuthWrap\Messages\WebServer\WebAppAccessTokenRequest.cs" />
diff --git a/src/DotNetOpenAuth/Messaging/Channel.cs b/src/DotNetOpenAuth/Messaging/Channel.cs
index 2e15fb5..385eb39 100644
--- a/src/DotNetOpenAuth/Messaging/Channel.cs
+++ b/src/DotNetOpenAuth/Messaging/Channel.cs
@@ -214,7 +214,7 @@ namespace DotNetOpenAuth.Messaging {
/// Gets or sets a tool that can figure out what kind of message is being received
/// so it can be deserialized.
/// </summary>
- protected IMessageFactory MessageFactory {
+ protected virtual IMessageFactory MessageFactory {
get { return this.messageTypeProvider; }
set { this.messageTypeProvider = value; }
}
diff --git a/src/DotNetOpenAuth/Messaging/StandardMessageFactoryChannel.cs b/src/DotNetOpenAuth/Messaging/StandardMessageFactoryChannel.cs
new file mode 100644
index 0000000..ab31d97
--- /dev/null
+++ b/src/DotNetOpenAuth/Messaging/StandardMessageFactoryChannel.cs
@@ -0,0 +1,92 @@
+//-----------------------------------------------------------------------
+// <copyright file="StandardMessageFactoryChannel.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Messaging {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Text;
+ using Reflection;
+
+ public abstract class StandardMessageFactoryChannel : Channel {
+ private readonly ICollection<Type> messageTypes;
+ private readonly ICollection<Version> versions;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StandardMessageFactoryChannel"/> class.
+ /// </summary>
+ /// <param name="bindingElements">The binding elements.</param>
+ protected StandardMessageFactoryChannel(ICollection<Type> messageTypes, ICollection<Version> versions, params IChannelBindingElement[] bindingElements)
+ : base(new StandardMessageFactory(), bindingElements) {
+ Contract.Requires<ArgumentNullException>(messageTypes != null, "messageTypes");
+ Contract.Requires<ArgumentNullException>(versions != null, "versions");
+
+ this.messageTypes = messageTypes;
+ this.versions = versions;
+ this.StandardMessageFactory.AddMessageTypes(GetMessageDescriptions(this.messageTypes, this.versions, this.MessageDescriptions));
+ }
+
+ /// <summary>
+ /// Gets or sets a tool that can figure out what kind of message is being received
+ /// so it can be deserialized.
+ /// </summary>
+ protected override IMessageFactory MessageFactory {
+ get {
+ return (StandardMessageFactory)base.MessageFactory;
+ }
+
+ set {
+ StandardMessageFactory newValue = (StandardMessageFactory)value;
+ base.MessageFactory = newValue;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets a tool that can figure out what kind of message is being received
+ /// so it can be deserialized.
+ /// </summary>
+ internal StandardMessageFactory StandardMessageFactory {
+ get { return (Messaging.StandardMessageFactory)this.MessageFactory; }
+ set { this.MessageFactory = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the message descriptions.
+ /// </summary>
+ internal override MessageDescriptionCollection MessageDescriptions {
+ get {
+ return base.MessageDescriptions;
+ }
+
+ set {
+ base.MessageDescriptions = value;
+
+ // We must reinitialize the message factory so it can use the new message descriptions.
+ var factory = new StandardMessageFactory();
+ factory.AddMessageTypes(GetMessageDescriptions(this.messageTypes, this.versions, value));
+ this.MessageFactory = factory;
+ }
+ }
+
+ private static IEnumerable<MessageDescription> GetMessageDescriptions(ICollection<Type> messageTypes, ICollection<Version> versions, MessageDescriptionCollection descriptionsCache)
+ {
+ Contract.Requires<ArgumentNullException>(messageTypes != null, "messageTypes");
+ Contract.Requires<ArgumentNullException>(descriptionsCache != null);
+ Contract.Ensures(Contract.Result<IEnumerable<MessageDescription>>() != null);
+
+ // Get all the MessageDescription objects through the standard cache,
+ // so that perhaps it will be a quick lookup, or at least it will be
+ // stored there for a quick lookup later.
+ var messageDescriptions = new List<MessageDescription>(messageTypes.Count * versions.Count);
+ messageDescriptions.AddRange(from version in versions
+ from messageType in messageTypes
+ select descriptionsCache.Get(messageType, version));
+
+ return messageDescriptions;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapChannel.cs b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapAuthorizationServerChannel.cs
index e99b2cf..4468c6c 100644
--- a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapChannel.cs
+++ b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapAuthorizationServerChannel.cs
@@ -1,5 +1,5 @@
//-----------------------------------------------------------------------
-// <copyright file="OAuthWrapChannel.cs" company="Andrew Arnott">
+// <copyright file="OAuthWrapAuthorizationServerChannel.cs" company="Andrew Arnott">
// Copyright (c) Andrew Arnott. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
@@ -18,31 +18,41 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements {
/// <summary>
/// The channel for the OAuth WRAP protocol.
/// </summary>
- internal class OAuthWrapChannel : Channel {
- /// <summary>
- /// Initializes a new instance of the <see cref="OAuthWrapChannel"/> class.
- /// </summary>
- protected internal OAuthWrapChannel()
- : base(new StandardMessageFactory()) {
- ((StandardMessageFactory)this.MessageFactory).AddMessageTypes(GetWrapMessageDescriptions(this.MessageDescriptions));
- }
+ internal class OAuthWrapAuthorizationServerChannel : StandardMessageFactoryChannel {
+ private static readonly Type[] MessageTypes = new Type[] {
+ typeof(Messages.RefreshAccessTokenRequest),
+ typeof(Messages.AccessTokenSuccessResponse),
+ typeof(Messages.AccessTokenFailedResponse),
+ typeof(Messages.UnauthorizedResponse),
+ typeof(Messages.AssertionRequest),
+ typeof(Messages.AssertionSuccessResponse),
+ typeof(Messages.ClientCredentialsRequest),
+ typeof(Messages.RichAppRequest),
+ typeof(Messages.RichAppResponse),
+ typeof(Messages.RichAppAccessTokenRequest),
+ typeof(Messages.RichAppAccessTokenSuccessResponse),
+ typeof(Messages.RichAppAccessTokenFailedResponse),
+ typeof(Messages.UserNamePasswordRequest),
+ typeof(Messages.UserNamePasswordSuccessResponse),
+ typeof(Messages.UserNamePasswordVerificationResponse),
+ typeof(Messages.UserNamePasswordFailedResponse),
+ typeof(Messages.UsernamePasswordCaptchaResponse),
+ typeof(Messages.WebAppRequest),
+ typeof(Messages.WebAppSuccessResponse),
+ typeof(Messages.WebAppFailedResponse),
+ typeof(Messages.WebAppAccessTokenRequest),
+ typeof(Messages.UserAgentRequest),
+ typeof(Messages.UserAgentSuccessResponse),
+ typeof(Messages.UserAgentFailedResponse),
+ };
+
+ private static readonly Version[] Versions = Protocol.AllVersions.Select(v => v.Version).ToArray();
/// <summary>
- /// Gets or sets the message descriptions.
+ /// Initializes a new instance of the <see cref="OAuthWrapChannel"/> class.
/// </summary>
- internal override MessageDescriptionCollection MessageDescriptions {
- get {
- return base.MessageDescriptions;
- }
-
- set {
- base.MessageDescriptions = value;
-
- // We must reinitialize the message factory so it can use the new message descriptions.
- var factory = new StandardMessageFactory();
- factory.AddMessageTypes(GetWrapMessageDescriptions(value));
- this.MessageFactory = factory;
- }
+ protected internal OAuthWrapAuthorizationServerChannel()
+ : base(MessageTypes, Versions) {
}
/// <summary>
@@ -103,54 +113,5 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements {
protected override OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response) {
throw new NotImplementedException();
}
-
- /// <summary>
- /// Gets the message types that come standard with OAuth WRAP.
- /// </summary>
- /// <param name="descriptionsCache">The descriptions cache from which to draw.</param>
- /// <returns>A collection of WRAP message types.</returns>
- private static IEnumerable<MessageDescription> GetWrapMessageDescriptions(MessageDescriptionCollection descriptionsCache) {
- Contract.Requires<ArgumentNullException>(descriptionsCache != null);
- Contract.Ensures(Contract.Result<IEnumerable<MessageDescription>>() != null);
-
- var messageTypes = new Type[] {
- typeof(Messages.RefreshAccessTokenRequest),
- typeof(Messages.AccessTokenSuccessResponse),
- typeof(Messages.AccessTokenFailedResponse),
- typeof(Messages.UnauthorizedResponse),
- typeof(Messages.AssertionRequest),
- typeof(Messages.AssertionSuccessResponse),
- typeof(Messages.ClientCredentialsRequest),
- typeof(Messages.RichAppRequest),
- typeof(Messages.RichAppResponse),
- typeof(Messages.RichAppAccessTokenRequest),
- typeof(Messages.RichAppAccessTokenSuccessResponse),
- typeof(Messages.RichAppAccessTokenFailedResponse),
- typeof(Messages.UserNamePasswordRequest),
- typeof(Messages.UserNamePasswordSuccessResponse),
- typeof(Messages.UserNamePasswordVerificationResponse),
- typeof(Messages.UserNamePasswordFailedResponse),
- typeof(Messages.UsernamePasswordCaptchaResponse),
- typeof(Messages.WebAppRequest),
- typeof(Messages.WebAppSuccessResponse),
- typeof(Messages.WebAppFailedResponse),
- typeof(Messages.WebAppAccessTokenRequest),
- typeof(Messages.UserAgentRequest),
- typeof(Messages.UserAgentSuccessResponse),
- typeof(Messages.UserAgentFailedResponse),
- };
-
- // Get all the MessageDescription objects through the standard cache,
- // so that perhaps it will be a quick lookup, or at least it will be
- // stored there for a quick lookup later.
- var messageDescriptions = new List<MessageDescription>(messageTypes.Length * Protocol.AllVersions.Count);
- foreach (Protocol protocol in Protocol.AllVersions) {
- foreach (Type messageType in messageTypes) {
- messageDescriptions.Add(descriptionsCache.Get(messageType, protocol.Version));
- }
- }
-
- return messageDescriptions;
- }
}
}
diff --git a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapResourceServerChannel.cs b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapResourceServerChannel.cs
new file mode 100644
index 0000000..3fefed5
--- /dev/null
+++ b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapResourceServerChannel.cs
@@ -0,0 +1,133 @@
+//-----------------------------------------------------------------------
+// <copyright file="OAuthWrapChannel.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuthWrap.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Net;
+ using System.Text;
+ using System.Web;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Reflection;
+
+ /// <summary>
+ /// The channel for the OAuth WRAP protocol.
+ /// </summary>
+ internal class OAuthWrapResourceServerChannel : StandardMessageFactoryChannel {
+ private static readonly Type[] MessageTypes = new Type[] {
+ typeof(Messages.AccessProtectedResourceRequest),
+ };
+
+ private static readonly Version[] Versions = Protocol.AllVersions.Select(v => v.Version).ToArray();
+
+ /// <summary>
+ /// A character array containing just the = character.
+ /// </summary>
+ private static readonly char[] EqualsArray = new char[] { '=' };
+
+ /// <summary>
+ /// A character array containing just the , character.
+ /// </summary>
+ private static readonly char[] CommaArray = new char[] { ',' };
+
+ /// <summary>
+ /// A character array containing just the " character.
+ /// </summary>
+ private static readonly char[] QuoteArray = new char[] { '"' };
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OAuthWrapResourceServerChannel"/> class.
+ /// </summary>
+ protected internal OAuthWrapResourceServerChannel()
+ : base(MessageTypes, Versions) {
+ // TODO: add signing (authenticated request) binding element.
+ }
+
+ private IEnumerable<KeyValuePair<string, string>> ParseAuthorizationHeader(string authorizationHeader) {
+ const string Prefix = Protocol.HttpAuthorizationScheme + " ";
+ if (authorizationHeader != null) {
+ string[] authorizationSections = authorizationHeader.Split(';'); // TODO: is this the right delimiter?
+ foreach (string authorization in authorizationSections) {
+ if (authorizationHeader.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase)) {
+ string data = authorizationHeader.Substring(Prefix.Length);
+ return from element in data.Split(CommaArray)
+ let parts = element.Split(EqualsArray, 2)
+ let key = Uri.UnescapeDataString(parts[0])
+ let value = Uri.UnescapeDataString(parts[1].Trim(QuoteArray))
+ select new KeyValuePair<string, string>(key, value);
+ }
+ }
+ }
+
+ return Enumerable.Empty<KeyValuePair<string, string>>();
+ }
+
+ /// <summary>
+ /// Gets the protocol message that may be embedded in the given HTTP request.
+ /// </summary>
+ /// <param name="request">The request to search for an embedded message.</param>
+ /// <returns>
+ /// The deserialized message, if one is found. Null otherwise.
+ /// </returns>
+ protected override IDirectedProtocolMessage ReadFromRequestCore(HttpRequestInfo request) {
+ var fields = new Dictionary<string, string>();
+
+ // First search the Authorization header.
+ var data = this.ParseAuthorizationHeader(request.Headers[HttpRequestHeader.Authorization])
+ .ToDictionary(pair => pair.Key, pair => pair.Value);
+ if (data.Count > 0) {
+ MessageReceivingEndpoint recipient;
+ try {
+ recipient = request.GetRecipient();
+ } catch (ArgumentException ex) {
+ Logger.OAuth.WarnFormat("Unrecognized HTTP request: " + ex.ToString());
+ return null;
+ }
+
+ // TODO: remove this after signatures are supported.
+ ErrorUtilities.VerifyProtocol(!fields.ContainsKey("signature"), "OAuth signatures not supported yet.");
+
+ // Deserialize the message using all the data we've collected.
+ var message = (IDirectedProtocolMessage)this.Receive(fields, recipient);
+ return message;
+ }
+
+ return base.ReadFromRequestCore(request);
+ }
+
+ /// <summary>
+ /// Gets the protocol message that may be in the given HTTP response.
+ /// </summary>
+ /// <param name="response">The response that is anticipated to contain an protocol message.</param>
+ /// <returns>
+ /// The deserialized message parts, if found. Null otherwise.
+ /// </returns>
+ /// <exception cref="ProtocolException">Thrown when the response is not valid.</exception>
+ protected override IDictionary<string, string> ReadFromResponseCore(IncomingWebResponse response) {
+ // We never expect resource servers to send out direct requests,
+ // and therefore won't have direct responses.
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Queues a message for sending in the response stream where the fields
+ /// are sent in the response stream in querystring style.
+ /// </summary>
+ /// <param name="response">The message to send as a response.</param>
+ /// <returns>
+ /// The pending user agent redirect based message to be sent as an HttpResponse.
+ /// </returns>
+ /// <remarks>
+ /// This method implements spec OAuth V1.0 section 5.3.
+ /// </remarks>
+ protected override OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response) {
+ throw new NotImplementedException();
+ }
+
+ }
+}
diff --git a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/TimestampEncoder.cs b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/TimestampEncoder.cs
new file mode 100644
index 0000000..c23488c
--- /dev/null
+++ b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/TimestampEncoder.cs
@@ -0,0 +1,50 @@
+//-----------------------------------------------------------------------
+// <copyright file="TimestampEncoder.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuthWrap.ChannelElements {
+ using System;
+ using System.Globalization;
+ using DotNetOpenAuth.Messaging.Reflection;
+
+ internal class TimestampEncoder : IMessagePartEncoder {
+ /// <summary>
+ /// The reference date and time for calculating time stamps.
+ /// </summary>
+ private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TimestampEncoder"/> class.
+ /// </summary>
+ internal TimestampEncoder() {
+ }
+
+ /// <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) {
+ var timestamp = (DateTime) value;
+ TimeSpan secondsSinceEpoch = timestamp - Epoch;
+ return secondsSinceEpoch.TotalSeconds.ToString(CultureInfo.InvariantCulture);
+ }
+
+ /// <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) {
+ var secondsSinceEpoch = Convert.ToInt32(value, CultureInfo.InvariantCulture);
+ return Epoch.AddSeconds(secondsSinceEpoch);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/OAuthWrap/ClientBase.cs b/src/DotNetOpenAuth/OAuthWrap/ClientBase.cs
index d5746bc..ec3dbc7 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 OAuthWrapChannel();
+ this.Channel = new OAuthWrapAuthorizationServerChannel();
}
/// <summary>
diff --git a/src/DotNetOpenAuth/OAuthWrap/IAccessTokenAnalyzer.cs b/src/DotNetOpenAuth/OAuthWrap/IAccessTokenAnalyzer.cs
new file mode 100644
index 0000000..85ae9d5
--- /dev/null
+++ b/src/DotNetOpenAuth/OAuthWrap/IAccessTokenAnalyzer.cs
@@ -0,0 +1,16 @@
+//-----------------------------------------------------------------------
+// <copyright file="IAccessTokenAnalyzer.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuthWrap {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+
+ public interface IAccessTokenAnalyzer {
+ bool TryValidateAccessToken(string accessToken, out string user, out string scope);
+ }
+}
diff --git a/src/DotNetOpenAuth/OAuthWrap/Messages/AccessProtectedResourceRequest.cs b/src/DotNetOpenAuth/OAuthWrap/Messages/AccessProtectedResourceRequest.cs
new file mode 100644
index 0000000..7b5569a
--- /dev/null
+++ b/src/DotNetOpenAuth/OAuthWrap/Messages/AccessProtectedResourceRequest.cs
@@ -0,0 +1,74 @@
+//-----------------------------------------------------------------------
+// <copyright file="AccessProtectedResourceRequest.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;
+ using ChannelElements;
+ using Messaging;
+
+ internal class AccessProtectedResourceRequest : MessageBase {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AccessProtectedResourceRequest"/> class.
+ /// </summary>
+ /// <param name="version">The version.</param>
+ /// <param name="recipient">The recipient.</param>
+ internal AccessProtectedResourceRequest(Version version, Uri recipient)
+ : base(version, MessageTransport.Direct, recipient) {
+ }
+
+ [MessagePart("token", IsRequired = true, AllowEmpty = false)]
+ internal string AccessToken { get; set; }
+
+ [MessagePart("nonce")]
+ internal string Nonce { get; set; }
+
+ [MessagePart("timestamp", Encoder = typeof(TimestampEncoder))]
+ internal DateTime? Timestamp { get; set; }
+
+ [MessagePart("signature")]
+ internal string Signature { get; set; }
+
+ [MessagePart("algorithm")]
+ internal string Algorithm { get; set; }
+
+ /// <summary>
+ /// Gets a value indicating whether this request is signed.
+ /// </summary>
+ internal bool SignedRequest {
+ get { return this.Signature != null; }
+ }
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ /// <remarks>
+ /// <para>Some messages have required fields, or combinations of fields that must relate to each other
+ /// in specialized ways. After deserializing a message, this method checks the state of the
+ /// message to see if it conforms to the protocol.</para>
+ /// <para>Note that this property should <i>not</i> check signatures or perform any state checks
+ /// outside this scope of this particular message.</para>
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception>
+ protected override void EnsureValidMessage() {
+ base.EnsureValidMessage();
+
+ // If any of the optional parameters are present, all of them are required.
+ if (this.Signature == null) {
+ ErrorUtilities.VerifyProtocol(this.Algorithm == null, this, MessagingStrings.UnexpectedMessagePartValue, "algorithm", this.Algorithm);
+ ErrorUtilities.VerifyProtocol(!this.Timestamp.HasValue, this, MessagingStrings.UnexpectedMessagePartValue, "timestamp", this.Timestamp);
+ ErrorUtilities.VerifyProtocol(this.Nonce == null, this, MessagingStrings.UnexpectedMessagePartValue, "nonce", this.Nonce);
+ } else {
+ ErrorUtilities.VerifyProtocol(this.Algorithm != null, this, MessagingStrings.RequiredParametersMissing, "algorithm");
+ ErrorUtilities.VerifyProtocol(this.Timestamp.HasValue, this, MessagingStrings.RequiredParametersMissing, "timestamp");
+ ErrorUtilities.VerifyProtocol(this.Nonce != null, this, MessagingStrings.RequiredParametersMissing, "nonce");
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/OAuthWrap/Protocol.cs b/src/DotNetOpenAuth/OAuthWrap/Protocol.cs
index c11c127..8dc10ee 100644
--- a/src/DotNetOpenAuth/OAuthWrap/Protocol.cs
+++ b/src/DotNetOpenAuth/OAuthWrap/Protocol.cs
@@ -24,9 +24,9 @@ namespace DotNetOpenAuth.OAuthWrap {
/// </summary>
internal class Protocol {
/// <summary>
- /// The HTTP authorization scheme "WRAP";
+ /// The HTTP authorization scheme "Token";
/// </summary>
- internal const string HttpAuthorizationScheme = "WRAP";
+ internal const string HttpAuthorizationScheme = "Token";
/// <summary>
/// The format of the HTTP Authorization header value that authorizes OAuth WRAP requests.
diff --git a/src/DotNetOpenAuth/OAuthWrap/ResourceServer.cs b/src/DotNetOpenAuth/OAuthWrap/ResourceServer.cs
new file mode 100644
index 0000000..a79aa86
--- /dev/null
+++ b/src/DotNetOpenAuth/OAuthWrap/ResourceServer.cs
@@ -0,0 +1,95 @@
+//-----------------------------------------------------------------------
+// <copyright file="ResourceServer.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuthWrap {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Net;
+ using System.Text;
+ using System.Text.RegularExpressions;
+ using System.Web;
+ using ChannelElements;
+ using Messages;
+ using Messaging;
+
+ /// <summary>
+ /// Provides services for validating OAuth access tokens.
+ /// </summary>
+ public class ResourceServer {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ResourceServer"/> class.
+ /// </summary>
+ /// <param name="accessTokenAnalyzer">The access token analyzer.</param>
+ public ResourceServer(IAccessTokenAnalyzer accessTokenAnalyzer) {
+ Contract.Requires<ArgumentNullException>(accessTokenAnalyzer != null, "accessTokenAnalyzer");
+
+ this.AccessTokenAnalyzer = accessTokenAnalyzer;
+ this.Channel = new OAuthWrapResourceServerChannel();
+ }
+
+ /// <summary>
+ /// Gets the access token analyzer.
+ /// </summary>
+ /// <value>The access token analyzer.</value>
+ public IAccessTokenAnalyzer AccessTokenAnalyzer { get; private set; }
+
+ /// <summary>
+ /// Gets or sets the endpoint information for an authorization server that may be used
+ /// to obtain an access token for this resource.
+ /// </summary>
+ /// <value>The authorization server description.</value>
+ /// <remarks>
+ /// This value is optional. If set, this information will be used to generate
+ /// more useful HTTP 401 Unauthorized responses for requests that lack an OAuth access token.
+ /// </remarks>
+ public AuthorizationServerDescription AuthorizationServerDescription { get; set; }
+
+ /// <summary>
+ /// Gets or sets the channel.
+ /// </summary>
+ /// <value>The channel.</value>
+ internal OAuthWrapResourceServerChannel Channel { get; private set; }
+
+ public OutgoingWebResponse VerifyAccess(out string username, out string scope) {
+ return this.VerifyAccess(this.Channel.GetRequestFromContext(), out username, out scope);
+ }
+
+ public virtual OutgoingWebResponse VerifyAccess(HttpRequestInfo httpRequestInfo, out string username, out string scope) {
+ Contract.Requires<ArgumentNullException>(httpRequestInfo != null, "httpRequestInfo");
+
+ try {
+ AccessProtectedResourceRequest request;
+ if (this.Channel.TryReadFromRequest<AccessProtectedResourceRequest>(httpRequestInfo, out request)) {
+ if (this.AccessTokenAnalyzer.TryValidateAccessToken(request.AccessToken, out username, out scope)) {
+ // No errors to return.
+ return null;
+ }
+
+ throw ErrorUtilities.ThrowProtocol("Bad access token");
+ } else {
+ throw ErrorUtilities.ThrowProtocol("Missing access token.");
+ }
+ } catch (ProtocolException ex) {
+ var unauthorizedError = new OutgoingWebResponse {
+ Status = HttpStatusCode.Unauthorized,
+ };
+
+ var authenticate = new StringBuilder();
+ authenticate.Append("Token ");
+ authenticate.AppendFormat("realm='{0}'", "Service");
+ authenticate.Append(",");
+ authenticate.AppendFormat("error=\"{0}\"", ex.Message);
+ unauthorizedError.Headers.Add(HttpResponseHeader.WwwAuthenticate, authenticate.ToString());
+
+ username = null;
+ scope = null;
+ return unauthorizedError;
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/OAuthWrap/WebAppAuthorizationServer.cs b/src/DotNetOpenAuth/OAuthWrap/WebAppAuthorizationServer.cs
index 91ce097..79b9682 100644
--- a/src/DotNetOpenAuth/OAuthWrap/WebAppAuthorizationServer.cs
+++ b/src/DotNetOpenAuth/OAuthWrap/WebAppAuthorizationServer.cs
@@ -14,6 +14,10 @@ namespace DotNetOpenAuth.OAuthWrap {
using DotNetOpenAuth.OAuthWrap.Messages;
public class WebAppAuthorizationServer : AuthorizationServerBase {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="WebAppAuthorizationServer"/> class.
+ /// </summary>
+ /// <param name="authorizationServer">The authorization server.</param>
public WebAppAuthorizationServer(IAuthorizationServer authorizationServer)
: base(authorizationServer) {
Contract.Requires<ArgumentNullException>(authorizationServer != null, "authorizationServer");
@@ -67,7 +71,7 @@ namespace DotNetOpenAuth.OAuthWrap {
return this.Channel.PrepareResponse(response);
}
- public OutgoingWebResponse RejectAuthorizationRequest(WebAppRequest authorizationRequest) {
+ public OutgoingWebResponse RejectAuthorizationRequest(WebAppRequest authorizationRequest, bool a=false) {
Contract.Requires<ArgumentNullException>(authorizationRequest != null, "authorizationRequest");
Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null);