diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2010-05-29 07:15:50 -0700 |
---|---|---|
committer | Andrew Arnott <andrewarnott@gmail.com> | 2010-05-29 07:15:50 -0700 |
commit | b7f504882c34b21db3f98333663f750c2c56d83f (patch) | |
tree | da13b362d580b0dc588dd8713ec1827fcce87196 /src | |
parent | 75a1fa8ad5d62e3a8241a2e796c903099c130020 (diff) | |
download | DotNetOpenAuth-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.cs | 6 | ||||
-rw-r--r-- | src/DotNetOpenAuth/DotNetOpenAuth.csproj | 8 | ||||
-rw-r--r-- | src/DotNetOpenAuth/Messaging/Channel.cs | 2 | ||||
-rw-r--r-- | src/DotNetOpenAuth/Messaging/StandardMessageFactoryChannel.cs | 92 | ||||
-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.cs | 133 | ||||
-rw-r--r-- | src/DotNetOpenAuth/OAuthWrap/ChannelElements/TimestampEncoder.cs | 50 | ||||
-rw-r--r-- | src/DotNetOpenAuth/OAuthWrap/ClientBase.cs | 2 | ||||
-rw-r--r-- | src/DotNetOpenAuth/OAuthWrap/IAccessTokenAnalyzer.cs | 16 | ||||
-rw-r--r-- | src/DotNetOpenAuth/OAuthWrap/Messages/AccessProtectedResourceRequest.cs | 74 | ||||
-rw-r--r-- | src/DotNetOpenAuth/OAuthWrap/Protocol.cs | 4 | ||||
-rw-r--r-- | src/DotNetOpenAuth/OAuthWrap/ResourceServer.cs | 95 | ||||
-rw-r--r-- | src/DotNetOpenAuth/OAuthWrap/WebAppAuthorizationServer.cs | 6 |
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); |