diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2012-03-31 11:45:42 -0700 |
---|---|---|
committer | Andrew Arnott <andrewarnott@gmail.com> | 2012-03-31 11:45:42 -0700 |
commit | af226f837b7bb5050ab511e66ba75714f79d8865 (patch) | |
tree | 3dba68ac08d55fa46e2b5c0b52c96d6612b12a2a /src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2 | |
parent | b4aa4d4cf25f358e8ca199fe3fbd446d1bb9bc42 (diff) | |
parent | 7265452c16667c6ff499970b0d6778d5184cc8cb (diff) | |
download | DotNetOpenAuth-af226f837b7bb5050ab511e66ba75714f79d8865.zip DotNetOpenAuth-af226f837b7bb5050ab511e66ba75714f79d8865.tar.gz DotNetOpenAuth-af226f837b7bb5050ab511e66ba75714f79d8865.tar.bz2 |
Applied some refactoring of OAuth2 classes.
Diffstat (limited to 'src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2')
3 files changed, 283 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ChannelElements/OAuth2ResourceServerChannel.cs b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ChannelElements/OAuth2ResourceServerChannel.cs new file mode 100644 index 0000000..22514b4 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ChannelElements/OAuth2ResourceServerChannel.cs @@ -0,0 +1,153 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuth2ResourceServerChannel.cs" company="Outercurve Foundation"> +// Copyright (c) Outercurve Foundation. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.ChannelElements { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Net; + using System.Net.Mime; + using System.Text; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Reflection; + using DotNetOpenAuth.OAuth2.Messages; + + /// <summary> + /// The channel for the OAuth protocol. + /// </summary> + internal class OAuth2ResourceServerChannel : StandardMessageFactoryChannel { + /// <summary> + /// The messages receivable by this channel. + /// </summary> + private static readonly Type[] MessageTypes = new Type[] { + typeof(Messages.AccessProtectedResourceRequest), + }; + + /// <summary> + /// The protocol versions supported by this channel. + /// </summary> + private static readonly Version[] Versions = Protocol.AllVersions.Select(v => v.Version).ToArray(); + + /// <summary> + /// Initializes a new instance of the <see cref="OAuth2ResourceServerChannel"/> class. + /// </summary> + protected internal OAuth2ResourceServerChannel() + : base(MessageTypes, Versions) { + // TODO: add signing (authenticated request) binding element. + } + + /// <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(HttpRequestBase request) { + var fields = new Dictionary<string, string>(); + string accessToken; + if ((accessToken = SearchForBearerAccessTokenInRequest(request)) != null) { + fields[Protocol.token_type] = Protocol.AccessTokenTypes.Bearer; + fields[Protocol.access_token] = accessToken; + } + + if (fields.Count > 0) { + MessageReceivingEndpoint recipient; + try { + recipient = request.GetRecipient(); + } catch (ArgumentException ex) { + Logger.OAuth.WarnFormat("Unrecognized HTTP request: " + ex.ToString()); + return null; + } + + // Deserialize the message using all the data we've collected. + var message = (IDirectedProtocolMessage)this.Receive(fields, recipient); + return message; + } + + return null; + } + + /// <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) { + var webResponse = new OutgoingWebResponse(); + + // The only direct response from a resource server is a 401 Unauthorized error. + var unauthorizedResponse = response as UnauthorizedResponse; + ErrorUtilities.VerifyInternal(unauthorizedResponse != null, "Only unauthorized responses are expected."); + + // First initialize based on the specifics within the message. + ApplyMessageTemplate(response, webResponse); + if (!(response is IHttpDirectResponse)) { + webResponse.Status = HttpStatusCode.Unauthorized; + } + + // Now serialize all the message parts into the WWW-Authenticate header. + var fields = this.MessageDescriptions.GetAccessor(response); + webResponse.Headers[HttpResponseHeader.WwwAuthenticate] = MessagingUtilities.AssembleAuthorizationHeader(Protocol.BearerHttpAuthorizationScheme, fields); + return webResponse; + } + + /// <summary> + /// Searches for a bearer access token in the request. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>The bearer access token, if one exists. Otherwise <c>null</c>.</returns> + private static string SearchForBearerAccessTokenInRequest(HttpRequestBase request) { + Requires.NotNull(request, "request"); + + // First search the authorization header. + string authorizationHeader = request.Headers[HttpRequestHeaders.Authorization]; + if (!string.IsNullOrEmpty(authorizationHeader) && authorizationHeader.StartsWith(Protocol.BearerHttpAuthorizationSchemeWithTrailingSpace, StringComparison.OrdinalIgnoreCase)) { + return authorizationHeader.Substring(Protocol.BearerHttpAuthorizationSchemeWithTrailingSpace.Length); + } + + // Failing that, scan the entity + if (!string.IsNullOrEmpty(request.Headers[HttpRequestHeaders.ContentType])) { + var contentType = new ContentType(request.Headers[HttpRequestHeaders.ContentType]); + if (string.Equals(contentType.MediaType, HttpFormUrlEncoded, StringComparison.Ordinal)) { + if (request.Form[Protocol.BearerTokenEncodedUrlParameterName] != null) { + return request.Form[Protocol.BearerTokenEncodedUrlParameterName]; + } + } + } + + // Finally, check the least desirable location: the query string + var unrewrittenQuery = request.GetQueryStringBeforeRewriting(); + if (!string.IsNullOrEmpty(unrewrittenQuery[Protocol.BearerTokenEncodedUrlParameterName])) { + return unrewrittenQuery[Protocol.BearerTokenEncodedUrlParameterName]; + } + + return null; + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/IAccessTokenAnalyzer.cs b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/IAccessTokenAnalyzer.cs new file mode 100644 index 0000000..5aa1bb6 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/IAccessTokenAnalyzer.cs @@ -0,0 +1,64 @@ +//----------------------------------------------------------------------- +// <copyright file="IAccessTokenAnalyzer.cs" company="Outercurve Foundation"> +// Copyright (c) Outercurve Foundation. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2 { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// An interface that resource server hosts should implement if they accept access tokens + /// issued by non-DotNetOpenAuth authorization servers. + /// </summary> + [ContractClass((typeof(IAccessTokenAnalyzerContract)))] + public interface IAccessTokenAnalyzer { + /// <summary> + /// Reads an access token to find out what data it authorizes access to. + /// </summary> + /// <param name="message">The message carrying the access token.</param> + /// <param name="accessToken">The access token.</param> + /// <param name="user">The user whose data is accessible with this access token.</param> + /// <param name="scope">The scope of access authorized by this access token.</param> + /// <returns>A value indicating whether this access token is valid.</returns> + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Try pattern")] + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "Try pattern")] + bool TryValidateAccessToken(IDirectedProtocolMessage message, string accessToken, out string user, out HashSet<string> scope); + } + + /// <summary> + /// Code contract for the <see cref="IAccessTokenAnalyzer"/> interface. + /// </summary> + [ContractClassFor(typeof(IAccessTokenAnalyzer))] + internal abstract class IAccessTokenAnalyzerContract : IAccessTokenAnalyzer { + /// <summary> + /// Prevents a default instance of the <see cref="IAccessTokenAnalyzerContract"/> class from being created. + /// </summary> + private IAccessTokenAnalyzerContract() { + } + + /// <summary> + /// Reads an access token to find out what data it authorizes access to. + /// </summary> + /// <param name="message">The message carrying the access token.</param> + /// <param name="accessToken">The access token.</param> + /// <param name="user">The user whose data is accessible with this access token.</param> + /// <param name="scope">The scope of access authorized by this access token.</param> + /// <returns> + /// A value indicating whether this access token is valid. + /// </returns> + bool IAccessTokenAnalyzer.TryValidateAccessToken(IDirectedProtocolMessage message, string accessToken, out string user, out HashSet<string> scope) { + Requires.NotNull(message, "message"); + Requires.NotNullOrEmpty(accessToken, "accessToken"); + Contract.Ensures(Contract.Result<bool>() == (Contract.ValueAtReturn<string>(out user) != null)); + + throw new NotImplementedException(); + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/StandardAccessTokenAnalyzer.cs b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/StandardAccessTokenAnalyzer.cs new file mode 100644 index 0000000..636f490 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/StandardAccessTokenAnalyzer.cs @@ -0,0 +1,66 @@ +//----------------------------------------------------------------------- +// <copyright file="StandardAccessTokenAnalyzer.cs" company="Outercurve Foundation"> +// Copyright (c) Outercurve Foundation. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2 { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Security.Cryptography; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth2.ChannelElements; + + /// <summary> + /// An access token reader that understands DotNetOpenAuth authorization server issued tokens. + /// </summary> + public class StandardAccessTokenAnalyzer : IAccessTokenAnalyzer { + /// <summary> + /// Initializes a new instance of the <see cref="StandardAccessTokenAnalyzer"/> class. + /// </summary> + /// <param name="authorizationServerPublicSigningKey">The crypto service provider with the authorization server public signing key.</param> + /// <param name="resourceServerPrivateEncryptionKey">The crypto service provider with the resource server private encryption key.</param> + public StandardAccessTokenAnalyzer(RSACryptoServiceProvider authorizationServerPublicSigningKey, RSACryptoServiceProvider resourceServerPrivateEncryptionKey) { + Requires.NotNull(authorizationServerPublicSigningKey, "authorizationServerPublicSigningKey"); + Requires.NotNull(resourceServerPrivateEncryptionKey, "resourceServerPrivateEncryptionKey"); + Requires.True(!resourceServerPrivateEncryptionKey.PublicOnly, "resourceServerPrivateEncryptionKey"); + this.AuthorizationServerPublicSigningKey = authorizationServerPublicSigningKey; + this.ResourceServerPrivateEncryptionKey = resourceServerPrivateEncryptionKey; + } + + /// <summary> + /// Gets the authorization server public signing key. + /// </summary> + /// <value>The authorization server public signing key.</value> + public RSACryptoServiceProvider AuthorizationServerPublicSigningKey { get; private set; } + + /// <summary> + /// Gets the resource server private encryption key. + /// </summary> + /// <value>The resource server private encryption key.</value> + public RSACryptoServiceProvider ResourceServerPrivateEncryptionKey { get; private set; } + + /// <summary> + /// Reads an access token to find out what data it authorizes access to. + /// </summary> + /// <param name="message">The message carrying the access token.</param> + /// <param name="accessToken">The access token.</param> + /// <param name="user">The user whose data is accessible with this access token.</param> + /// <param name="scope">The scope of access authorized by this access token.</param> + /// <returns> + /// A value indicating whether this access token is valid. + /// </returns> + /// <remarks> + /// This method also responsible to throw a <see cref="ProtocolException"/> or return + /// <c>false</c> when the access token is expired, invalid, or from an untrusted authorization server. + /// </remarks> + public virtual bool TryValidateAccessToken(IDirectedProtocolMessage message, string accessToken, out string user, out HashSet<string> scope) { + var accessTokenFormatter = AccessToken.CreateFormatter(this.AuthorizationServerPublicSigningKey, this.ResourceServerPrivateEncryptionKey); + var token = accessTokenFormatter.Deserialize(message, accessToken, Protocol.access_token); + user = token.User; + scope = new HashSet<string>(token.Scope, OAuthUtilities.ScopeStringComparer); + return true; + } + } +} |