summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2012-03-31 11:45:42 -0700
committerAndrew Arnott <andrewarnott@gmail.com>2012-03-31 11:45:42 -0700
commitaf226f837b7bb5050ab511e66ba75714f79d8865 (patch)
tree3dba68ac08d55fa46e2b5c0b52c96d6612b12a2a /src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2
parentb4aa4d4cf25f358e8ca199fe3fbd446d1bb9bc42 (diff)
parent7265452c16667c6ff499970b0d6778d5184cc8cb (diff)
downloadDotNetOpenAuth-af226f837b7bb5050ab511e66ba75714f79d8865.zip
DotNetOpenAuth-af226f837b7bb5050ab511e66ba75714f79d8865.tar.gz
DotNetOpenAuth-af226f837b7bb5050ab511e66ba75714f79d8865.tar.bz2
Applied some refactoring of OAuth2 classes.
Diffstat (limited to 'src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2')
-rw-r--r--src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ChannelElements/OAuth2ResourceServerChannel.cs153
-rw-r--r--src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/IAccessTokenAnalyzer.cs64
-rw-r--r--src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/StandardAccessTokenAnalyzer.cs66
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;
+ }
+ }
+}