diff options
Diffstat (limited to 'src/DotNetOpenAuth.OAuth2.ResourceServer')
8 files changed, 420 insertions, 47 deletions
diff --git a/src/DotNetOpenAuth.OAuth2.ResourceServer/Configuration/OAuth2ResourceServerSection.cs b/src/DotNetOpenAuth.OAuth2.ResourceServer/Configuration/OAuth2ResourceServerSection.cs new file mode 100644 index 0000000..3e37018 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.ResourceServer/Configuration/OAuth2ResourceServerSection.cs @@ -0,0 +1,36 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuth2ResourceServerSection.cs" company="Outercurve Foundation"> +// Copyright (c) Outercurve Foundation. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Configuration { + using System.Configuration; + using System.Diagnostics.Contracts; + + /// <summary> + /// Represents the <oauth2/resourceServer> section in the host's .config file. + /// </summary> + internal class OAuth2ResourceServerSection : ConfigurationElement { + /// <summary> + /// The name of the oauth2/client section. + /// </summary> + private const string SectionName = OAuth2SectionGroup.SectionName + "/resourceServer"; + + /// <summary> + /// Initializes a new instance of the <see cref="OAuth2ResourceServerSection"/> class. + /// </summary> + internal OAuth2ResourceServerSection() { + } + + /// <summary> + /// Gets the configuration section from the .config file. + /// </summary> + internal static OAuth2ResourceServerSection Configuration { + get { + Contract.Ensures(Contract.Result<OAuth2ResourceServerSection>() != null); + return (OAuth2ResourceServerSection)ConfigurationManager.GetSection(SectionName) ?? new OAuth2ResourceServerSection(); + } + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.ResourceServer/DotNetOpenAuth.OAuth2.ResourceServer.csproj b/src/DotNetOpenAuth.OAuth2.ResourceServer/DotNetOpenAuth.OAuth2.ResourceServer.csproj index 63806b8..eb54fee 100644 --- a/src/DotNetOpenAuth.OAuth2.ResourceServer/DotNetOpenAuth.OAuth2.ResourceServer.csproj +++ b/src/DotNetOpenAuth.OAuth2.ResourceServer/DotNetOpenAuth.OAuth2.ResourceServer.csproj @@ -18,12 +18,16 @@ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> </PropertyGroup> <ItemGroup> + <Compile Include="Configuration\OAuth2ResourceServerSection.cs" /> + <Compile Include="OAuth2\ChannelElements\OAuth2ResourceServerChannel.cs" /> + <Compile Include="OAuth2\IAccessTokenAnalyzer.cs" /> <Compile Include="OAuth2\ResourceServerStrings.Designer.cs"> <AutoGen>True</AutoGen> <DesignTime>True</DesignTime> <DependentUpon>ResourceServerStrings.resx</DependentUpon> </Compile> <Compile Include="OAuth2\ResourceServer.cs" /> + <Compile Include="OAuth2\StandardAccessTokenAnalyzer.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> </ItemGroup> <ItemGroup> 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..e9d596a --- /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 some authorization error (400, 401, 403). + 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(unauthorizedResponse.Scheme, 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..5c5a526 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/IAccessTokenAnalyzer.cs @@ -0,0 +1,57 @@ +//----------------------------------------------------------------------- +// <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's serialized representation.</param> + /// <returns>The deserialized, validated token.</returns> + /// <exception cref="ProtocolException">Thrown if the access token is expired, invalid, or from an untrusted authorization server.</exception> + AccessToken DeserializeAccessToken(IDirectedProtocolMessage message, string accessToken); + } + + /// <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's serialized representation.</param> + /// <returns>The deserialized, validated token.</returns> + /// <exception cref="ProtocolException">Thrown if the access token is expired, invalid, or from an untrusted authorization server.</exception> + AccessToken IAccessTokenAnalyzer.DeserializeAccessToken(IDirectedProtocolMessage message, string accessToken) { + Requires.NotNull(message, "message"); + Requires.NotNullOrEmpty(accessToken, "accessToken"); + Contract.Ensures(Contract.Result<AccessToken>() != null); + throw new NotImplementedException(); + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServer.cs b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServer.cs index 2830ab8..cd0fb55 100644 --- a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServer.cs +++ b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServer.cs @@ -26,6 +26,11 @@ namespace DotNetOpenAuth.OAuth2 { /// </summary> public class ResourceServer { /// <summary> + /// A reusable instance of the scope satisfied checker. + /// </summary> + private static readonly IScopeSatisfiedCheck DefaultScopeSatisfiedCheck = new StandardScopeSatisfiedCheck(); + + /// <summary> /// Initializes a new instance of the <see cref="ResourceServer"/> class. /// </summary> /// <param name="accessTokenAnalyzer">The access token analyzer.</param> @@ -34,6 +39,9 @@ namespace DotNetOpenAuth.OAuth2 { this.AccessTokenAnalyzer = accessTokenAnalyzer; this.Channel = new OAuth2ResourceServerChannel(); + this.ResourceOwnerPrincipalPrefix = string.Empty; + this.ClientPrincipalPrefix = "client:"; + this.ScopeSatisfiedCheck = DefaultScopeSatisfiedCheck; } /// <summary> @@ -43,59 +51,78 @@ namespace DotNetOpenAuth.OAuth2 { public IAccessTokenAnalyzer AccessTokenAnalyzer { get; private set; } /// <summary> - /// Gets the channel. + /// Gets or sets the service that checks whether a granted set of scopes satisfies a required set of scopes. /// </summary> - /// <value>The channel.</value> - internal OAuth2ResourceServerChannel Channel { get; private set; } + public IScopeSatisfiedCheck ScopeSatisfiedCheck { get; set; } /// <summary> - /// Discovers what access the client should have considering the access token in the current request. + /// Gets or sets the prefix to apply to a resource owner's username when used as the username in an <see cref="IPrincipal"/>. /// </summary> - /// <param name="userName">The name on the account the client has access to.</param> - /// <param name="scope">The set of operations the client is authorized for.</param> - /// <returns>An error to return to the client if access is not authorized; <c>null</c> if access is granted.</returns> - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#", Justification = "Try pattern")] - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Try pattern")] - public OutgoingWebResponse VerifyAccess(out string userName, out HashSet<string> scope) { - return this.VerifyAccess(this.Channel.GetRequestFromContext(), out userName, out scope); - } + /// <value>The default value is the empty string.</value> + public string ResourceOwnerPrincipalPrefix { get; set; } + + /// <summary> + /// Gets or sets the prefix to apply to a client identifier when used as the username in an <see cref="IPrincipal"/>. + /// </summary> + /// <value>The default value is "client:"</value> + public string ClientPrincipalPrefix { get; set; } + + /// <summary> + /// Gets the channel. + /// </summary> + /// <value>The channel.</value> + internal OAuth2ResourceServerChannel Channel { get; private set; } /// <summary> /// Discovers what access the client should have considering the access token in the current request. /// </summary> /// <param name="httpRequestInfo">The HTTP request info.</param> - /// <param name="userName">The name on the account the client has access to.</param> - /// <param name="scope">The set of operations the client is authorized for.</param> + /// <param name="requiredScopes">The set of scopes required to approve this request.</param> /// <returns> - /// An error to return to the client if access is not authorized; <c>null</c> if access is granted. + /// The access token describing the authorization the client has. Never <c>null</c>. /// </returns> - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Try pattern")] - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "Try pattern")] - public virtual OutgoingWebResponse VerifyAccess(HttpRequestBase httpRequestInfo, out string userName, out HashSet<string> scope) { - Requires.NotNull(httpRequestInfo, "httpRequestInfo"); + /// <exception cref="ProtocolFaultResponseException"> + /// Thrown when the client is not authorized. This exception should be caught and the + /// <see cref="ProtocolFaultResponseException.ErrorResponseMessage"/> message should be returned to the client. + /// </exception> + public virtual AccessToken GetAccessToken(HttpRequestBase httpRequestInfo = null, params string[] requiredScopes) { + Requires.NotNull(requiredScopes, "requiredScopes"); + Requires.ValidState(this.ScopeSatisfiedCheck != null, Strings.RequiredPropertyNotYetPreset); + if (httpRequestInfo == null) { + httpRequestInfo = this.Channel.GetRequestFromContext(); + } + AccessToken accessToken; AccessProtectedResourceRequest request = null; try { if (this.Channel.TryReadFromRequest<AccessProtectedResourceRequest>(httpRequestInfo, out request)) { - if (this.AccessTokenAnalyzer.TryValidateAccessToken(request, request.AccessToken, out userName, out scope)) { - // No errors to return. - return null; + accessToken = this.AccessTokenAnalyzer.DeserializeAccessToken(request, request.AccessToken); + ErrorUtilities.VerifyHost(accessToken != null, "IAccessTokenAnalyzer.DeserializeAccessToken returned a null reslut."); + if (string.IsNullOrEmpty(accessToken.User) && string.IsNullOrEmpty(accessToken.ClientIdentifier)) { + Logger.OAuth.Error("Access token rejected because both the username and client id properties were null or empty."); + ErrorUtilities.ThrowProtocol(ResourceServerStrings.InvalidAccessToken); } - throw ErrorUtilities.ThrowProtocol(ResourceServerStrings.InvalidAccessToken); - } else { - var response = new UnauthorizedResponse(new ProtocolException(ResourceServerStrings.MissingAccessToken)); + var requiredScopesSet = OAuthUtilities.ParseScopeSet(requiredScopes); + if (!this.ScopeSatisfiedCheck.IsScopeSatisfied(requiredScope: requiredScopesSet, grantedScope: accessToken.Scope)) { + var response = UnauthorizedResponse.InsufficientScope(request, requiredScopesSet); + throw new ProtocolFaultResponseException(this.Channel, response); + } - userName = null; - scope = null; - return this.Channel.PrepareResponse(response); + return accessToken; + } else { + var ex = new ProtocolException(ResourceServerStrings.MissingAccessToken); + var response = UnauthorizedResponse.InvalidRequest(ex); + throw new ProtocolFaultResponseException(this.Channel, response, innerException: ex); } } catch (ProtocolException ex) { - var response = request != null ? new UnauthorizedResponse(request, ex) : new UnauthorizedResponse(ex); + if (ex is ProtocolFaultResponseException) { + // This doesn't need to be wrapped again. + throw; + } - userName = null; - scope = null; - return this.Channel.PrepareResponse(response); + var response = request != null ? UnauthorizedResponse.InvalidToken(request, ex) : UnauthorizedResponse.InvalidRequest(ex); + throw new ProtocolFaultResponseException(this.Channel, response, innerException: ex); } } @@ -103,17 +130,29 @@ namespace DotNetOpenAuth.OAuth2 { /// Discovers what access the client should have considering the access token in the current request. /// </summary> /// <param name="httpRequestInfo">The HTTP request info.</param> - /// <param name="principal">The principal that contains the user and roles that the access token is authorized for.</param> + /// <param name="requiredScopes">The set of scopes required to approve this request.</param> /// <returns> - /// An error to return to the client if access is not authorized; <c>null</c> if access is granted. + /// The principal that contains the user and roles that the access token is authorized for. Never <c>null</c>. /// </returns> - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Try pattern")] - public virtual OutgoingWebResponse VerifyAccess(HttpRequestBase httpRequestInfo, out IPrincipal principal) { - string username; - HashSet<string> scope; - var result = this.VerifyAccess(httpRequestInfo, out username, out scope); - principal = result == null ? new OAuthPrincipal(username, scope != null ? scope.ToArray() : new string[0]) : null; - return result; + /// <exception cref="ProtocolFaultResponseException"> + /// Thrown when the client is not authorized. This exception should be caught and the + /// <see cref="ProtocolFaultResponseException.ErrorResponseMessage"/> message should be returned to the client. + /// </exception> + public virtual IPrincipal GetPrincipal(HttpRequestBase httpRequestInfo = null, params string[] requiredScopes) { + AccessToken accessToken = this.GetAccessToken(httpRequestInfo, requiredScopes); + + // Mitigates attacks on this approach of differentiating clients from resource owners + // by checking that a username doesn't look suspiciously engineered to appear like the other type. + ErrorUtilities.VerifyProtocol(accessToken.User == null || string.IsNullOrEmpty(this.ClientPrincipalPrefix) || !accessToken.User.StartsWith(this.ClientPrincipalPrefix, StringComparison.OrdinalIgnoreCase), ResourceServerStrings.ResourceOwnerNameLooksLikeClientIdentifier); + ErrorUtilities.VerifyProtocol(accessToken.ClientIdentifier == null || string.IsNullOrEmpty(this.ResourceOwnerPrincipalPrefix) || !accessToken.ClientIdentifier.StartsWith(this.ResourceOwnerPrincipalPrefix, StringComparison.OrdinalIgnoreCase), ResourceServerStrings.ClientIdentifierLooksLikeResourceOwnerName); + + string principalUserName = !string.IsNullOrEmpty(accessToken.User) + ? this.ResourceOwnerPrincipalPrefix + accessToken.User + : this.ClientPrincipalPrefix + accessToken.ClientIdentifier; + string[] principalScope = accessToken.Scope != null ? accessToken.Scope.ToArray() : new string[0]; + var principal = new OAuthPrincipal(principalUserName, principalScope); + + return principal; } /// <summary> @@ -121,17 +160,19 @@ namespace DotNetOpenAuth.OAuth2 { /// </summary> /// <param name="request">HTTP details from an incoming WCF message.</param> /// <param name="requestUri">The URI of the WCF service endpoint.</param> - /// <param name="principal">The principal that contains the user and roles that the access token is authorized for.</param> + /// <param name="requiredScopes">The set of scopes required to approve this request.</param> /// <returns> - /// An error to return to the client if access is not authorized; <c>null</c> if access is granted. + /// The principal that contains the user and roles that the access token is authorized for. Never <c>null</c>. /// </returns> - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Try pattern")] - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "Try pattern")] - public virtual OutgoingWebResponse VerifyAccess(HttpRequestMessageProperty request, Uri requestUri, out IPrincipal principal) { + /// <exception cref="ProtocolFaultResponseException"> + /// Thrown when the client is not authorized. This exception should be caught and the + /// <see cref="ProtocolFaultResponseException.ErrorResponseMessage"/> message should be returned to the client. + /// </exception> + public virtual IPrincipal GetPrincipal(HttpRequestMessageProperty request, Uri requestUri, params string[] requiredScopes) { Requires.NotNull(request, "request"); Requires.NotNull(requestUri, "requestUri"); - return this.VerifyAccess(new HttpRequestInfo(request, requestUri), out principal); + return this.GetPrincipal(new HttpRequestInfo(request, requestUri), requiredScopes); } } } diff --git a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServerStrings.Designer.cs b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServerStrings.Designer.cs index 606b072..f97b41b 100644 --- a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServerStrings.Designer.cs +++ b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServerStrings.Designer.cs @@ -61,6 +61,15 @@ namespace DotNetOpenAuth.OAuth2 { } /// <summary> + /// Looks up a localized string similar to Client Identifier starts with a resource owner prefix. Authorization aborted.. + /// </summary> + internal static string ClientIdentifierLooksLikeResourceOwnerName { + get { + return ResourceManager.GetString("ClientIdentifierLooksLikeResourceOwnerName", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to Invalid access token.. /// </summary> internal static string InvalidAccessToken { @@ -77,5 +86,14 @@ namespace DotNetOpenAuth.OAuth2 { return ResourceManager.GetString("MissingAccessToken", resourceCulture); } } + + /// <summary> + /// Looks up a localized string similar to Resource owner username starts with a client prefix. Authorization aborted.. + /// </summary> + internal static string ResourceOwnerNameLooksLikeClientIdentifier { + get { + return ResourceManager.GetString("ResourceOwnerNameLooksLikeClientIdentifier", resourceCulture); + } + } } } diff --git a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServerStrings.resx b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServerStrings.resx index 175a386..46943c4 100644 --- a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServerStrings.resx +++ b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServerStrings.resx @@ -117,10 +117,16 @@ <resheader name="writer"> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> </resheader> + <data name="ClientIdentifierLooksLikeResourceOwnerName" xml:space="preserve"> + <value>Client Identifier starts with a resource owner prefix. Authorization aborted.</value> + </data> <data name="InvalidAccessToken" xml:space="preserve"> <value>Invalid access token.</value> </data> <data name="MissingAccessToken" xml:space="preserve"> <value>Missing access token.</value> </data> + <data name="ResourceOwnerNameLooksLikeClientIdentifier" xml:space="preserve"> + <value>Resource owner username starts with a client prefix. Authorization aborted.</value> + </data> </root>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/StandardAccessTokenAnalyzer.cs b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/StandardAccessTokenAnalyzer.cs new file mode 100644 index 0000000..54d86ff --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/StandardAccessTokenAnalyzer.cs @@ -0,0 +1,58 @@ +//----------------------------------------------------------------------- +// <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's serialized representation.</param> + /// <returns>The deserialized, validated token.</returns> + /// <exception cref="ProtocolException">Thrown if the access token is expired, invalid, or from an untrusted authorization server.</exception> + public virtual AccessToken DeserializeAccessToken(IDirectedProtocolMessage message, string accessToken) { + var accessTokenFormatter = AccessToken.CreateFormatter(this.AuthorizationServerPublicSigningKey, this.ResourceServerPrivateEncryptionKey); + var token = new AccessToken(); + accessTokenFormatter.Deserialize(token, message, accessToken, Protocol.access_token); + return token; + } + } +} |