diff options
Diffstat (limited to 'src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2')
23 files changed, 2018 insertions, 24 deletions
diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerStrings.Designer.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerStrings.Designer.cs new file mode 100644 index 0000000..4b4f830 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerStrings.Designer.cs @@ -0,0 +1,117 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:4.0.30319.17614 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace DotNetOpenAuth.OAuth2 { + using System; + + + /// <summary> + /// A strongly-typed resource class, for looking up localized strings, etc. + /// </summary> + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class AuthServerStrings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal AuthServerStrings() { + } + + /// <summary> + /// Returns the cached ResourceManager instance used by this class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DotNetOpenAuth.OAuth2.AuthServerStrings", typeof(AuthServerStrings).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// <summary> + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// <summary> + /// Looks up a localized string similar to The requested access scope exceeds the grant scope.. + /// </summary> + internal static string AccessScopeExceedsGrantScope { + get { + return ResourceManager.GetString("AccessScopeExceedsGrantScope", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The callback URL ({0}) is not allowed for this client.. + /// </summary> + internal static string ClientCallbackDisallowed { + get { + return ResourceManager.GetString("ClientCallbackDisallowed", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Failure looking up secret for client or token.. + /// </summary> + internal static string ClientOrTokenSecretNotFound { + get { + return ResourceManager.GetString("ClientOrTokenSecretNotFound", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The client secret was incorrect.. + /// </summary> + internal static string ClientSecretMismatch { + get { + return ResourceManager.GetString("ClientSecretMismatch", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Invalid resource owner password credential.. + /// </summary> + internal static string InvalidResourceOwnerPasswordCredential { + get { + return ResourceManager.GetString("InvalidResourceOwnerPasswordCredential", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to No callback URI was available for this request.. + /// </summary> + internal static string NoCallback { + get { + return ResourceManager.GetString("NoCallback", resourceCulture); + } + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerStrings.resx b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerStrings.resx new file mode 100644 index 0000000..29d841a --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerStrings.resx @@ -0,0 +1,138 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="AccessScopeExceedsGrantScope" xml:space="preserve"> + <value>The requested access scope exceeds the grant scope.</value> + </data> + <data name="ClientCallbackDisallowed" xml:space="preserve"> + <value>The callback URL ({0}) is not allowed for this client.</value> + </data> + <data name="ClientOrTokenSecretNotFound" xml:space="preserve"> + <value>Failure looking up secret for client or token.</value> + </data> + <data name="ClientSecretMismatch" xml:space="preserve"> + <value>The client secret was incorrect.</value> + </data> + <data name="InvalidResourceOwnerPasswordCredential" xml:space="preserve"> + <value>Invalid resource owner password credential.</value> + </data> + <data name="NoCallback" xml:space="preserve"> + <value>No callback URI was available for this request.</value> + </data> +</root>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerUtilities.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerUtilities.cs new file mode 100644 index 0000000..b8a1071 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerUtilities.cs @@ -0,0 +1,65 @@ +//----------------------------------------------------------------------- +// <copyright file="AuthServerUtilities.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.Globalization; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth2.ChannelElements; + using DotNetOpenAuth.OAuth2.Messages; + + /// <summary> + /// Utility methods for authorization servers. + /// </summary> + internal static class AuthServerUtilities { + /// <summary> + /// Gets information about the client with a given identifier. + /// </summary> + /// <param name="authorizationServer">The authorization server.</param> + /// <param name="clientIdentifier">The client identifier.</param> + /// <returns>The client information. Never null.</returns> + internal static IClientDescription GetClientOrThrow(this IAuthorizationServerHost authorizationServer, string clientIdentifier) { + Requires.NotNullOrEmpty(clientIdentifier, "clientIdentifier"); + Contract.Ensures(Contract.Result<IClientDescription>() != null); + + try { + var result = authorizationServer.GetClient(clientIdentifier); + ErrorUtilities.VerifyHost(result != null, OAuthStrings.ResultShouldNotBeNull, authorizationServer.GetType().FullName, "GetClient(string)"); + return result; + } catch (KeyNotFoundException ex) { + throw ErrorUtilities.Wrap(ex, AuthServerStrings.ClientOrTokenSecretNotFound); + } catch (ArgumentException ex) { + throw ErrorUtilities.Wrap(ex, AuthServerStrings.ClientOrTokenSecretNotFound); + } + } + + /// <summary> + /// Verifies a condition is true or throws an exception describing the problem. + /// </summary> + /// <param name="condition">The condition that evaluates to true to avoid an exception.</param> + /// <param name="requestMessage">The request message.</param> + /// <param name="error">A single error code from <see cref="Protocol.AccessTokenRequestErrorCodes"/>.</param> + /// <param name="authenticationModule">The authentication module from which to glean the WWW-Authenticate header when applicable.</param> + /// <param name="unformattedDescription">A human-readable UTF-8 encoded text providing additional information, used to assist the client developer in understanding the error that occurred.</param> + /// <param name="args">The formatting arguments to generate the actual description.</param> + internal static void TokenEndpointVerify(bool condition, AccessTokenRequestBase requestMessage, string error, ClientAuthenticationModule authenticationModule = null, string unformattedDescription = null, params object[] args) { + if (!condition) { + string description = unformattedDescription != null ? string.Format(CultureInfo.CurrentCulture, unformattedDescription, args) : null; + + string wwwAuthenticateHeader = null; + if (authenticationModule != null) { + wwwAuthenticateHeader = authenticationModule.AuthenticateHeader; + } + + throw new TokenEndpointProtocolException(requestMessage, error, description, authenticateHeader: wwwAuthenticateHeader); + } + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs index f555248..6a96c2d 100644 --- a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs @@ -13,7 +13,7 @@ namespace DotNetOpenAuth.OAuth2 { using System.Security.Cryptography; using System.Text; using System.Web; - + using DotNetOpenAuth.Configuration; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth2.ChannelElements; using DotNetOpenAuth.OAuth2.Messages; @@ -23,12 +23,30 @@ namespace DotNetOpenAuth.OAuth2 { /// </summary> public class AuthorizationServer { /// <summary> + /// A reusable instance of the scope satisfied checker. + /// </summary> + private static readonly IScopeSatisfiedCheck DefaultScopeSatisfiedCheck = new StandardScopeSatisfiedCheck(); + + /// <summary> + /// The list of modules that verify client authentication data. + /// </summary> + private readonly List<ClientAuthenticationModule> clientAuthenticationModules = new List<ClientAuthenticationModule>(); + + /// <summary> + /// The lone aggregate client authentication module that uses the <see cref="clientAuthenticationModules"/> and applies aggregating policy. + /// </summary> + private readonly ClientAuthenticationModule aggregatingClientAuthenticationModule; + + /// <summary> /// Initializes a new instance of the <see cref="AuthorizationServer"/> class. /// </summary> /// <param name="authorizationServer">The authorization server.</param> - public AuthorizationServer(IAuthorizationServer authorizationServer) { + public AuthorizationServer(IAuthorizationServerHost authorizationServer) { Requires.NotNull(authorizationServer, "authorizationServer"); - this.Channel = new OAuth2AuthorizationServerChannel(authorizationServer); + this.aggregatingClientAuthenticationModule = new AggregatingClientCredentialReader(this.clientAuthenticationModules); + this.Channel = new OAuth2AuthorizationServerChannel(authorizationServer, this.aggregatingClientAuthenticationModule); + this.clientAuthenticationModules.AddRange(OAuth2AuthorizationServerSection.Configuration.ClientAuthenticationModules.CreateInstances(true)); + this.ScopeSatisfiedCheck = DefaultScopeSatisfiedCheck; } /// <summary> @@ -41,11 +59,26 @@ namespace DotNetOpenAuth.OAuth2 { /// Gets the authorization server. /// </summary> /// <value>The authorization server.</value> - public IAuthorizationServer AuthorizationServerServices { + public IAuthorizationServerHost AuthorizationServerServices { get { return ((IOAuth2ChannelWithAuthorizationServer)this.Channel).AuthorizationServer; } } /// <summary> + /// Gets the extension modules that can read client authentication data from incoming messages. + /// </summary> + public IList<ClientAuthenticationModule> ClientAuthenticationModules { + get { return this.clientAuthenticationModules; } + } + + /// <summary> + /// Gets or sets the service that checks whether a granted set of scopes satisfies a required set of scopes. + /// </summary> + public IScopeSatisfiedCheck ScopeSatisfiedCheck { + get { return ((IOAuth2ChannelWithAuthorizationServer)this.Channel).ScopeSatisfiedCheck; } + set { ((IOAuth2ChannelWithAuthorizationServer)this.Channel).ScopeSatisfiedCheck = value; } + } + + /// <summary> /// Reads in a client's request for the Authorization Server to obtain permission from /// the user to authorize the Client's access of some protected resource(s). /// </summary> @@ -63,7 +96,7 @@ namespace DotNetOpenAuth.OAuth2 { if (message.ResponseType == EndUserAuthorizationResponseType.AuthorizationCode) { // Clients with no secrets can only request implicit grant types. var client = this.AuthorizationServerServices.GetClientOrThrow(message.ClientIdentifier); - ErrorUtilities.VerifyProtocol(!string.IsNullOrEmpty(client.Secret), Protocol.unauthorized_client); + ErrorUtilities.VerifyProtocol(client.HasNonEmptySecret, Protocol.EndUserAuthorizationRequestErrorCodes.UnauthorizedClient); } } @@ -110,17 +143,30 @@ namespace DotNetOpenAuth.OAuth2 { IProtocolMessage responseMessage; try { if (this.Channel.TryReadFromRequest(request, out requestMessage)) { - // TODO: refreshToken should be set appropriately based on authorization server policy. - responseMessage = this.PrepareAccessTokenResponse(requestMessage); + var accessTokenResult = this.AuthorizationServerServices.CreateAccessToken(requestMessage); + ErrorUtilities.VerifyHost(accessTokenResult != null, "IAuthorizationServerHost.CreateAccessToken must not return null."); + + IAccessTokenRequestInternal accessRequestInternal = requestMessage; + accessRequestInternal.AccessTokenResult = accessTokenResult; + + var successResponseMessage = this.PrepareAccessTokenResponse(requestMessage, accessTokenResult.AllowRefreshToken); + successResponseMessage.Lifetime = accessTokenResult.AccessToken.Lifetime; + + var authCarryingRequest = requestMessage as IAuthorizationCarryingRequest; + if (authCarryingRequest != null) { + accessTokenResult.AccessToken.ApplyAuthorization(authCarryingRequest.AuthorizationDescription); + IAccessTokenIssuingResponse accessTokenIssuingResponse = successResponseMessage; + accessTokenIssuingResponse.AuthorizationDescription = accessTokenResult.AccessToken; + } + + responseMessage = successResponseMessage; } else { - responseMessage = new AccessTokenFailedResponse() { - Error = Protocol.AccessTokenRequestErrorCodes.InvalidRequest, - }; + responseMessage = new AccessTokenFailedResponse() { Error = Protocol.AccessTokenRequestErrorCodes.InvalidRequest }; } + } catch (TokenEndpointProtocolException ex) { + responseMessage = ex.GetResponse(); } catch (ProtocolException) { - responseMessage = new AccessTokenFailedResponse() { - Error = Protocol.AccessTokenRequestErrorCodes.InvalidRequest, - }; + responseMessage = new AccessTokenFailedResponse() { Error = Protocol.AccessTokenRequestErrorCodes.InvalidRequest }; } return this.Channel.PrepareResponse(responseMessage); @@ -165,12 +211,30 @@ namespace DotNetOpenAuth.OAuth2 { EndUserAuthorizationSuccessResponseBase response; switch (authorizationRequest.ResponseType) { case EndUserAuthorizationResponseType.AccessToken: - var accessTokenResponse = new EndUserAuthorizationSuccessAccessTokenResponse(callback, authorizationRequest); - accessTokenResponse.Lifetime = this.AuthorizationServerServices.GetAccessTokenLifetime((EndUserAuthorizationImplicitRequest)authorizationRequest); - response = accessTokenResponse; + IAccessTokenRequestInternal accessRequestInternal = (EndUserAuthorizationImplicitRequest)authorizationRequest; + var accessTokenResult = this.AuthorizationServerServices.CreateAccessToken(accessRequestInternal); + ErrorUtilities.VerifyHost(accessTokenResult != null, "IAuthorizationServerHost.CreateAccessToken must not return null."); + + accessRequestInternal.AccessTokenResult = accessTokenResult; + + var implicitGrantResponse = new EndUserAuthorizationSuccessAccessTokenResponse(callback, authorizationRequest); + implicitGrantResponse.Lifetime = accessTokenResult.AccessToken.Lifetime; + accessTokenResult.AccessToken.ApplyAuthorization(implicitGrantResponse.Scope, userName, implicitGrantResponse.Lifetime); + + IAccessTokenCarryingRequest tokenCarryingResponse = implicitGrantResponse; + tokenCarryingResponse.AuthorizationDescription = accessTokenResult.AccessToken; + + response = implicitGrantResponse; break; case EndUserAuthorizationResponseType.AuthorizationCode: - response = new EndUserAuthorizationSuccessAuthCodeResponse(callback, authorizationRequest); + var authCodeResponse = new EndUserAuthorizationSuccessAuthCodeResponseAS(callback, authorizationRequest); + IAuthorizationCodeCarryingRequest codeCarryingResponse = authCodeResponse; + codeCarryingResponse.AuthorizationDescription = new AuthorizationCode( + authorizationRequest.ClientIdentifier, + authorizationRequest.Callback, + authCodeResponse.Scope, + userName); + response = authCodeResponse; break; default: throw ErrorUtilities.ThrowInternal("Unexpected response type."); @@ -208,7 +272,7 @@ namespace DotNetOpenAuth.OAuth2 { // Since the request didn't include a callback URL, look up the callback from // the client's preregistration with this authorization server. Uri defaultCallback = client.DefaultCallback; - ErrorUtilities.VerifyProtocol(defaultCallback != null, OAuthStrings.NoCallback); + ErrorUtilities.VerifyProtocol(defaultCallback != null, AuthServerStrings.NoCallback); return defaultCallback; } @@ -216,24 +280,24 @@ namespace DotNetOpenAuth.OAuth2 { /// Prepares the response to an access token request. /// </summary> /// <param name="request">The request for an access token.</param> - /// <param name="includeRefreshToken">If set to <c>true</c>, the response will include a long-lived refresh token.</param> + /// <param name="allowRefreshToken">If set to <c>true</c>, the response will include a long-lived refresh token.</param> /// <returns>The response message to send to the client.</returns> - private IDirectResponseProtocolMessage PrepareAccessTokenResponse(AccessTokenRequestBase request, bool includeRefreshToken = true) { + private AccessTokenSuccessResponse PrepareAccessTokenResponse(AccessTokenRequestBase request, bool allowRefreshToken = true) { Requires.NotNull(request, "request"); - if (includeRefreshToken) { + if (allowRefreshToken) { if (request is AccessTokenClientCredentialsRequest) { // Per OAuth 2.0 section 4.4.3 (draft 23), refresh tokens should never be included // in a response to an access token request that used the client credential grant type. Logger.OAuth.Debug("Suppressing refresh token in access token response because the grant type used by the client disallows it."); - includeRefreshToken = false; + allowRefreshToken = false; } } var tokenRequest = (IAuthorizationCarryingRequest)request; + var accessTokenRequest = (IAccessTokenRequestInternal)request; var response = new AccessTokenSuccessResponse(request) { - Lifetime = this.AuthorizationServerServices.GetAccessTokenLifetime(request), - HasRefreshToken = includeRefreshToken, + HasRefreshToken = allowRefreshToken, }; response.Scope.ResetContents(tokenRequest.AuthorizationDescription.Scope); return response; diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServerAccessToken.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServerAccessToken.cs new file mode 100644 index 0000000..c577a0a --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServerAccessToken.cs @@ -0,0 +1,52 @@ +//----------------------------------------------------------------------- +// <copyright file="AuthorizationServerAccessToken.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2 { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Security.Cryptography; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth2.ChannelElements; + + /// <summary> + /// An access token minted by the authorization server that can be serialized for transmission to the client. + /// </summary> + public class AuthorizationServerAccessToken : AccessToken { + /// <summary> + /// Initializes a new instance of the <see cref="AuthorizationServerAccessToken"/> class. + /// </summary> + public AuthorizationServerAccessToken() { + } + + /// <summary> + /// Gets or sets the crypto service provider with the asymmetric private key to use for signing access tokens. + /// </summary> + /// <returns>A crypto service provider instance that contains the private key.</returns> + /// <value>Must not be null, and must contain the private key.</value> + /// <remarks> + /// The public key in the private/public key pair will be used by the resource + /// servers to validate that the access token is minted by a trusted authorization server. + /// </remarks> + public RSACryptoServiceProvider AccessTokenSigningKey { get; set; } + + /// <summary> + /// Gets or sets the key to encrypt the access token. + /// </summary> + public RSACryptoServiceProvider ResourceServerEncryptionKey { get; set; } + + /// <summary> + /// Serializes this instance to a simple string for transmission to the client. + /// </summary> + /// <returns>A non-empty string.</returns> + protected internal override string Serialize() { + var formatter = CreateFormatter(this.AccessTokenSigningKey, this.ResourceServerEncryptionKey); + return formatter.Serialize(this); + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AggregatingClientCredentialReader.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AggregatingClientCredentialReader.cs new file mode 100644 index 0000000..ace95b3 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AggregatingClientCredentialReader.cs @@ -0,0 +1,91 @@ +//----------------------------------------------------------------------- +// <copyright file="AggregatingClientCredentialReader.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.ChannelElements { + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Text; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth2.Messages; + + /// <summary> + /// Applies OAuth 2 spec policy for supporting multiple methods of client authentication. + /// </summary> + internal class AggregatingClientCredentialReader : ClientAuthenticationModule { + /// <summary> + /// The set of authenticators to apply to an incoming request. + /// </summary> + private readonly IEnumerable<ClientAuthenticationModule> authenticators; + + /// <summary> + /// Initializes a new instance of the <see cref="AggregatingClientCredentialReader"/> class. + /// </summary> + /// <param name="authenticators">The set of authentication modules to apply.</param> + internal AggregatingClientCredentialReader(IEnumerable<ClientAuthenticationModule> authenticators) { + Requires.NotNull(authenticators, "readers"); + this.authenticators = authenticators; + } + + /// <summary> + /// Gets this module's contribution to an HTTP 401 WWW-Authenticate header so the client knows what kind of authentication this module supports. + /// </summary> + public override string AuthenticateHeader { + get { + var builder = new StringBuilder(); + foreach (var authenticator in this.authenticators) { + string scheme = authenticator.AuthenticateHeader; + if (scheme != null) { + if (builder.Length > 0) { + builder.Append(", "); + } + + builder.Append(scheme); + } + } + + return builder.Length > 0 ? builder.ToString() : null; + } + } + + /// <summary> + /// Attempts to extract client identification/authentication information from a message. + /// </summary> + /// <param name="authorizationServerHost">The authorization server host.</param> + /// <param name="requestMessage">The incoming message.</param> + /// <param name="clientIdentifier">Receives the client identifier, if one was found.</param> + /// <returns>The level of the extracted client information.</returns> + public override ClientAuthenticationResult TryAuthenticateClient(IAuthorizationServerHost authorizationServerHost, AuthenticatedClientRequestBase requestMessage, out string clientIdentifier) { + Requires.NotNull(authorizationServerHost, "authorizationServerHost"); + Requires.NotNull(requestMessage, "requestMessage"); + + ClientAuthenticationModule authenticator = null; + ClientAuthenticationResult result = ClientAuthenticationResult.NoAuthenticationRecognized; + clientIdentifier = null; + + foreach (var candidateAuthenticator in this.authenticators) { + string candidateClientIdentifier; + var resultCandidate = candidateAuthenticator.TryAuthenticateClient(authorizationServerHost, requestMessage, out candidateClientIdentifier); + + ErrorUtilities.VerifyProtocol( + result == ClientAuthenticationResult.NoAuthenticationRecognized || resultCandidate == ClientAuthenticationResult.NoAuthenticationRecognized, + "Message rejected because multiple forms of client authentication ({0} and {1}) were detected, which is forbidden by the OAuth 2 Protocol Framework specification.", + authenticator, + candidateAuthenticator); + + if (resultCandidate != ClientAuthenticationResult.NoAuthenticationRecognized) { + authenticator = candidateAuthenticator; + result = resultCandidate; + clientIdentifier = candidateClientIdentifier; + } + } + + return result; + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AuthServerBindingElementBase.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AuthServerBindingElementBase.cs new file mode 100644 index 0000000..9d3a52c --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AuthServerBindingElementBase.cs @@ -0,0 +1,88 @@ +//----------------------------------------------------------------------- +// <copyright file="AuthServerBindingElementBase.cs" company="Outercurve Foundation"> +// Copyright (c) Outercurve Foundation. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.ChannelElements { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using Messaging; + + /// <summary> + /// The base class for any authorization server channel binding element. + /// </summary> + internal abstract class AuthServerBindingElementBase : IChannelBindingElement { + /// <summary> + /// Initializes a new instance of the <see cref="AuthServerBindingElementBase"/> class. + /// </summary> + protected AuthServerBindingElementBase() { + } + + /// <summary> + /// Gets or sets the channel that this binding element belongs to. + /// </summary> + /// <remarks> + /// This property is set by the channel when it is first constructed. + /// </remarks> + public Channel Channel { get; set; } + + /// <summary> + /// Gets the protection commonly offered (if any) by this binding element. + /// </summary> + /// <remarks> + /// This value is used to assist in sorting binding elements in the channel stack. + /// </remarks> + public abstract MessageProtections Protection { get; } + + /// <summary> + /// Gets the channel to which this binding element belongs. + /// </summary> + internal IOAuth2ChannelWithAuthorizationServer AuthServerChannel { + get { return (IOAuth2ChannelWithAuthorizationServer)this.Channel; } + } + + /// <summary> + /// Gets the authorization server hosting this channel. + /// </summary> + /// <value>The authorization server.</value> + protected IAuthorizationServerHost AuthorizationServer { + get { return ((IOAuth2ChannelWithAuthorizationServer)this.Channel).AuthorizationServer; } + } + + /// <summary> + /// Prepares a message for sending based on the rules of this channel binding element. + /// </summary> + /// <param name="message">The message to prepare for sending.</param> + /// <returns> + /// The protections (if any) that this binding element applied to the message. + /// Null if this binding element did not even apply to this binding element. + /// </returns> + /// <remarks> + /// Implementations that provide message protection must honor the + /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable. + /// </remarks> + public abstract MessageProtections? ProcessOutgoingMessage(IProtocolMessage message); + + /// <summary> + /// Performs any transformation on an incoming message that may be necessary and/or + /// validates an incoming message based on the rules of this channel binding element. + /// </summary> + /// <param name="message">The incoming message to process.</param> + /// <returns> + /// The protections (if any) that this binding element applied to the message. + /// Null if this binding element did not even apply to this binding element. + /// </returns> + /// <exception cref="ProtocolException"> + /// Thrown when the binding element rules indicate that this message is invalid and should + /// NOT be processed. + /// </exception> + /// <remarks> + /// Implementations that provide message protection must honor the + /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable. + /// </remarks> + public abstract MessageProtections? ProcessIncomingMessage(IProtocolMessage message); + } +} diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AuthorizationCode.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AuthorizationCode.cs new file mode 100644 index 0000000..853a629 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AuthorizationCode.cs @@ -0,0 +1,118 @@ +//----------------------------------------------------------------------- +// <copyright file="AuthorizationCode.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.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Security.Cryptography; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Represents the authorization code created when a user approves authorization that + /// allows the client to request an access/refresh token. + /// </summary> + internal class AuthorizationCode : AuthorizationDataBag { + /// <summary> + /// The name of the bucket for symmetric keys used to sign authorization codes. + /// </summary> + internal const string AuthorizationCodeKeyBucket = "https://localhost/dnoa/oauth_authorization_code"; + + /// <summary> + /// Initializes a new instance of the <see cref="AuthorizationCode"/> class. + /// </summary> + public AuthorizationCode() { + } + + /// <summary> + /// Initializes a new instance of the <see cref="AuthorizationCode"/> class. + /// </summary> + /// <param name="clientIdentifier">The client identifier.</param> + /// <param name="callback">The callback the client used to obtain authorization, if one was explicitly included in the request.</param> + /// <param name="scopes">The authorized scopes.</param> + /// <param name="username">The name on the account that authorized access.</param> + internal AuthorizationCode(string clientIdentifier, Uri callback, IEnumerable<string> scopes, string username) { + Requires.NotNullOrEmpty(clientIdentifier, "clientIdentifier"); + + this.ClientIdentifier = clientIdentifier; + this.CallbackHash = CalculateCallbackHash(callback); + this.Scope.ResetContents(scopes); + this.User = username; + this.UtcCreationDate = DateTime.UtcNow; + } + + /// <summary> + /// Gets the maximum message age from the standard expiration binding element. + /// </summary> + /// <value>This interval need not account for clock skew because it is only compared within a single authorization server or farm of servers.</value> + internal static TimeSpan MaximumMessageAge { + get { return Configuration.DotNetOpenAuthSection.Messaging.MaximumMessageLifetimeNoSkew; } + } + + /// <summary> + /// Gets or sets the hash of the callback URL. + /// </summary> + [MessagePart("cb")] + private byte[] CallbackHash { get; set; } + + /// <summary> + /// Creates a serializer/deserializer for this type. + /// </summary> + /// <param name="authorizationServer">The authorization server that will be serializing/deserializing this authorization code. Must not be null.</param> + /// <returns>A DataBag formatter.</returns> + internal static IDataBagFormatter<AuthorizationCode> CreateFormatter(IAuthorizationServerHost authorizationServer) { + Requires.NotNull(authorizationServer, "authorizationServer"); + Contract.Ensures(Contract.Result<IDataBagFormatter<AuthorizationCode>>() != null); + + var cryptoStore = authorizationServer.CryptoKeyStore; + ErrorUtilities.VerifyHost(cryptoStore != null, OAuthStrings.ResultShouldNotBeNull, authorizationServer.GetType(), "CryptoKeyStore"); + + return new UriStyleMessageFormatter<AuthorizationCode>( + cryptoStore, + AuthorizationCodeKeyBucket, + signed: true, + encrypted: true, + compressed: false, + maximumAge: MaximumMessageAge, + decodeOnceOnly: authorizationServer.NonceStore); + } + + /// <summary> + /// Verifies the the given callback URL matches the callback originally given in the authorization request. + /// </summary> + /// <param name="callback">The callback.</param> + /// <remarks> + /// This method serves to verify that the callback URL given in the original authorization request + /// and the callback URL given in the access token request match. + /// </remarks> + /// <exception cref="ProtocolException">Thrown when the callback URLs do not match.</exception> + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "redirecturimismatch", Justification = "Protocol requirement")] + [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "DotNetOpenAuth.Messaging.ErrorUtilities.VerifyProtocol(System.Boolean,System.String,System.Object[])", Justification = "Protocol requirement")] + internal void VerifyCallback(Uri callback) { + ErrorUtilities.VerifyProtocol(MessagingUtilities.AreEquivalentConstantTime(this.CallbackHash, CalculateCallbackHash(callback)), Protocol.redirect_uri_mismatch); + } + + /// <summary> + /// Calculates the hash of the callback URL. + /// </summary> + /// <param name="callback">The callback whose hash should be calculated.</param> + /// <returns> + /// A base64 encoding of the hash of the URL. + /// </returns> + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "False positive.")] + private static byte[] CalculateCallbackHash(Uri callback) { + if (callback == null) { + return null; + } + + using (var hasher = new SHA256Managed()) { + return hasher.ComputeHash(Encoding.UTF8.GetBytes(callback.AbsoluteUri)); + } + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientAuthenticationModule.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientAuthenticationModule.cs new file mode 100644 index 0000000..027929a --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientAuthenticationModule.cs @@ -0,0 +1,74 @@ +//----------------------------------------------------------------------- +// <copyright file="ClientAuthenticationModule.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.ChannelElements { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth2.Messages; + + /// <summary> + /// A base class for extensions that can read incoming messages and extract the client identifier and + /// possibly authentication information (like a shared secret, signed nonce, etc.) + /// </summary> + public abstract class ClientAuthenticationModule { + /// <summary> + /// Initializes a new instance of the <see cref="ClientAuthenticationModule"/> class. + /// </summary> + protected ClientAuthenticationModule() { + } + + /// <summary> + /// Gets this module's contribution to an HTTP 401 WWW-Authenticate header so the client knows what kind of authentication this module supports. + /// </summary> + public virtual string AuthenticateHeader { + get { return null; } + } + + /// <summary> + /// Attempts to extract client identification/authentication information from a message. + /// </summary> + /// <param name="authorizationServerHost">The authorization server host.</param> + /// <param name="requestMessage">The incoming message.</param> + /// <param name="clientIdentifier">Receives the client identifier, if one was found.</param> + /// <returns>The level of the extracted client information.</returns> + public abstract ClientAuthenticationResult TryAuthenticateClient(IAuthorizationServerHost authorizationServerHost, AuthenticatedClientRequestBase requestMessage, out string clientIdentifier); + + /// <summary> + /// Validates a client identifier and shared secret against the authoriation server's database. + /// </summary> + /// <param name="authorizationServerHost">The authorization server host; cannot be <c>null</c>.</param> + /// <param name="clientIdentifier">The alleged client identifier.</param> + /// <param name="clientSecret">The alleged client secret to be verified.</param> + /// <returns>An indication as to the outcome of the validation.</returns> + protected static ClientAuthenticationResult TryAuthenticateClientBySecret(IAuthorizationServerHost authorizationServerHost, string clientIdentifier, string clientSecret) { + Requires.NotNull(authorizationServerHost, "authorizationServerHost"); + + if (!string.IsNullOrEmpty(clientIdentifier)) { + var client = authorizationServerHost.GetClient(clientIdentifier); + if (client != null) { + if (!string.IsNullOrEmpty(clientSecret)) { + if (client.IsValidClientSecret(clientSecret)) { + return ClientAuthenticationResult.ClientAuthenticated; + } else { // invalid client secret + return ClientAuthenticationResult.ClientAuthenticationRejected; + } + } else { // no client secret provided + return ClientAuthenticationResult.ClientIdNotAuthenticated; + } + } else { // The client identifier is not recognized. + return ClientAuthenticationResult.ClientAuthenticationRejected; + } + } else { // no client id provided. + return ClientAuthenticationResult.NoAuthenticationRecognized; + } + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialHttpBasicReader.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialHttpBasicReader.cs new file mode 100644 index 0000000..655d38f --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialHttpBasicReader.cs @@ -0,0 +1,48 @@ +//----------------------------------------------------------------------- +// <copyright file="ClientCredentialHttpBasicReader.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.ChannelElements { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth2.Messages; + + /// <summary> + /// Reads client authentication information from the HTTP Authorization header via Basic authentication. + /// </summary> + public class ClientCredentialHttpBasicReader : ClientAuthenticationModule { + /// <summary> + /// Gets this module's contribution to an HTTP 401 WWW-Authenticate header so the client knows what kind of authentication this module supports. + /// </summary> + public override string AuthenticateHeader { + get { return "Basic"; } + } + + /// <summary> + /// Attempts to extract client identification/authentication information from a message. + /// </summary> + /// <param name="authorizationServerHost">The authorization server host.</param> + /// <param name="requestMessage">The incoming message.</param> + /// <param name="clientIdentifier">Receives the client identifier, if one was found.</param> + /// <returns>The level of the extracted client information.</returns> + public override ClientAuthenticationResult TryAuthenticateClient(IAuthorizationServerHost authorizationServerHost, AuthenticatedClientRequestBase requestMessage, out string clientIdentifier) { + Requires.NotNull(authorizationServerHost, "authorizationServerHost"); + Requires.NotNull(requestMessage, "requestMessage"); + + var credential = OAuthUtilities.ParseHttpBasicAuth(requestMessage.Headers); + if (credential != null) { + clientIdentifier = credential.UserName; + return TryAuthenticateClientBySecret(authorizationServerHost, credential.UserName, credential.Password); + } + + clientIdentifier = null; + return ClientAuthenticationResult.NoAuthenticationRecognized; + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialMessagePartReader.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialMessagePartReader.cs new file mode 100644 index 0000000..2afd06e --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialMessagePartReader.cs @@ -0,0 +1,34 @@ +//----------------------------------------------------------------------- +// <copyright file="ClientCredentialMessagePartReader.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.ChannelElements { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Web; + using DotNetOpenAuth.OAuth2.Messages; + + /// <summary> + /// Reads client authentication information from the message payload itself (POST entity as a URI-encoded parameter). + /// </summary> + public class ClientCredentialMessagePartReader : ClientAuthenticationModule { + /// <summary> + /// Attempts to extract client identification/authentication information from a message. + /// </summary> + /// <param name="authorizationServerHost">The authorization server host.</param> + /// <param name="requestMessage">The incoming message.</param> + /// <param name="clientIdentifier">Receives the client identifier, if one was found.</param> + /// <returns>The level of the extracted client information.</returns> + public override ClientAuthenticationResult TryAuthenticateClient(IAuthorizationServerHost authorizationServerHost, AuthenticatedClientRequestBase requestMessage, out string clientIdentifier) { + Requires.NotNull(authorizationServerHost, "authorizationServerHost"); + Requires.NotNull(requestMessage, "requestMessage"); + + clientIdentifier = requestMessage.ClientIdentifier; + return TryAuthenticateClientBySecret(authorizationServerHost, requestMessage.ClientIdentifier, requestMessage.ClientSecret); + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/IOAuth2ChannelWithAuthorizationServer.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/IOAuth2ChannelWithAuthorizationServer.cs new file mode 100644 index 0000000..5247062 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/IOAuth2ChannelWithAuthorizationServer.cs @@ -0,0 +1,24 @@ +//----------------------------------------------------------------------- +// <copyright file="IOAuth2ChannelWithAuthorizationServer.cs" company="Outercurve Foundation"> +// Copyright (c) Outercurve Foundation. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.ChannelElements { + /// <summary> + /// An interface on an OAuth 2 Authorization Server channel + /// to expose the host provided authorization server object. + /// </summary> + internal interface IOAuth2ChannelWithAuthorizationServer { + /// <summary> + /// Gets the authorization server. + /// </summary> + /// <value>The authorization server.</value> + IAuthorizationServerHost AuthorizationServer { get; } + + /// <summary> + /// Gets or sets the service that checks whether a granted set of scopes satisfies a required set of scopes. + /// </summary> + IScopeSatisfiedCheck ScopeSatisfiedCheck { get; set; } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/MessageValidationBindingElement.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/MessageValidationBindingElement.cs new file mode 100644 index 0000000..80b843a --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/MessageValidationBindingElement.cs @@ -0,0 +1,202 @@ +//----------------------------------------------------------------------- +// <copyright file="MessageValidationBindingElement.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.Globalization; + using System.Linq; + using System.Text; + using DotNetOpenAuth.OAuth2.Messages; + using Messaging; + + /// <summary> + /// A guard for all messages to or from an Authorization Server to ensure that they are well formed, + /// have valid secrets, callback URIs, etc. + /// </summary> + /// <remarks> + /// This binding element also ensures that the code/token coming in is issued to + /// the same client that is sending the code/token and that the authorization has + /// not been revoked and that an access token has not expired. + /// </remarks> + internal class MessageValidationBindingElement : AuthServerBindingElementBase { + /// <summary> + /// The aggregating client authentication module. + /// </summary> + private readonly ClientAuthenticationModule clientAuthenticationModule; + + /// <summary> + /// Initializes a new instance of the <see cref="MessageValidationBindingElement"/> class. + /// </summary> + /// <param name="clientAuthenticationModule">The aggregating client authentication module.</param> + internal MessageValidationBindingElement(ClientAuthenticationModule clientAuthenticationModule) { + Requires.NotNull(clientAuthenticationModule, "clientAuthenticationModule"); + this.clientAuthenticationModule = clientAuthenticationModule; + } + + /// <summary> + /// Gets the protection commonly offered (if any) by this binding element. + /// </summary> + /// <remarks> + /// This value is used to assist in sorting binding elements in the channel stack. + /// </remarks> + public override MessageProtections Protection { + get { return MessageProtections.None; } + } + + /// <summary> + /// Prepares a message for sending based on the rules of this channel binding element. + /// </summary> + /// <param name="message">The message to prepare for sending.</param> + /// <returns> + /// The protections (if any) that this binding element applied to the message. + /// Null if this binding element did not even apply to this binding element. + /// </returns> + /// <remarks> + /// Implementations that provide message protection must honor the + /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable. + /// </remarks> + public override MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { + var accessTokenResponse = message as AccessTokenSuccessResponse; + if (accessTokenResponse != null) { + var directResponseMessage = (IDirectResponseProtocolMessage)accessTokenResponse; + var accessTokenRequest = (AccessTokenRequestBase)directResponseMessage.OriginatingRequest; + ErrorUtilities.VerifyProtocol(accessTokenRequest.GrantType != GrantType.ClientCredentials || accessTokenResponse.RefreshToken == null, OAuthStrings.NoGrantNoRefreshToken); + } + + return null; + } + + /// <summary> + /// Performs any transformation on an incoming message that may be necessary and/or + /// validates an incoming message based on the rules of this channel binding element. + /// </summary> + /// <param name="message">The incoming message to process.</param> + /// <returns> + /// The protections (if any) that this binding element applied to the message. + /// Null if this binding element did not even apply to this binding element. + /// </returns> + /// <exception cref="ProtocolException"> + /// Thrown when the binding element rules indicate that this message is invalid and should + /// NOT be processed. + /// </exception> + /// <remarks> + /// Implementations that provide message protection must honor the + /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable. + /// </remarks> + public override MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { + bool applied = false; + + // Check that the client secret is correct for client authenticated messages. + var clientCredentialOnly = message as AccessTokenClientCredentialsRequest; + var authenticatedClientRequest = message as AuthenticatedClientRequestBase; + var accessTokenRequest = authenticatedClientRequest as AccessTokenRequestBase; // currently the only type of message. + var resourceOwnerPasswordCarrier = message as AccessTokenResourceOwnerPasswordCredentialsRequest; + if (authenticatedClientRequest != null) { + string clientIdentifier; + var result = this.clientAuthenticationModule.TryAuthenticateClient(this.AuthServerChannel.AuthorizationServer, authenticatedClientRequest, out clientIdentifier); + switch (result) { + case ClientAuthenticationResult.ClientAuthenticated: + break; + case ClientAuthenticationResult.NoAuthenticationRecognized: + case ClientAuthenticationResult.ClientIdNotAuthenticated: + // The only grant type that allows no client credentials is the resource owner credentials grant. + AuthServerUtilities.TokenEndpointVerify(resourceOwnerPasswordCarrier != null, accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.InvalidClient, this.clientAuthenticationModule, AuthServerStrings.ClientSecretMismatch); + break; + default: + AuthServerUtilities.TokenEndpointVerify(false, accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.InvalidClient, this.clientAuthenticationModule, AuthServerStrings.ClientSecretMismatch); + break; + } + + authenticatedClientRequest.ClientIdentifier = result == ClientAuthenticationResult.NoAuthenticationRecognized ? null : clientIdentifier; + accessTokenRequest.ClientAuthenticated = result == ClientAuthenticationResult.ClientAuthenticated; + applied = true; + } + + // Check that any resource owner password credential is correct. + if (resourceOwnerPasswordCarrier != null) { + try { + string canonicalUserName; + if (this.AuthorizationServer.TryAuthorizeResourceOwnerCredentialGrant(resourceOwnerPasswordCarrier.UserName, resourceOwnerPasswordCarrier.Password, resourceOwnerPasswordCarrier, out canonicalUserName)) { + ErrorUtilities.VerifyHost(!string.IsNullOrEmpty(canonicalUserName), "IsResourceOwnerCredentialValid did not initialize out parameter."); + resourceOwnerPasswordCarrier.CredentialsValidated = true; + resourceOwnerPasswordCarrier.UserName = canonicalUserName; + } else { + Logger.OAuth.ErrorFormat( + "Resource owner password credential for user \"{0}\" rejected by authorization server host.", + resourceOwnerPasswordCarrier.UserName); + throw new TokenEndpointProtocolException(accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.InvalidGrant, AuthServerStrings.InvalidResourceOwnerPasswordCredential); + } + } catch (NotSupportedException) { + throw new TokenEndpointProtocolException(accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.UnsupportedGrantType); + } catch (NotImplementedException) { + throw new TokenEndpointProtocolException(accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.UnsupportedGrantType); + } + + applied = true; + } else if (clientCredentialOnly != null) { + try { + if (!this.AuthorizationServer.TryAuthorizeClientCredentialsGrant(clientCredentialOnly)) { + Logger.OAuth.ErrorFormat( + "Client credentials grant access request for client \"{0}\" rejected by authorization server host.", + clientCredentialOnly.ClientIdentifier); + throw new TokenEndpointProtocolException(accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.UnauthorizedClient); + } + } catch (NotSupportedException) { + throw new TokenEndpointProtocolException(accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.UnsupportedGrantType); + } catch (NotImplementedException) { + throw new TokenEndpointProtocolException(accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.UnsupportedGrantType); + } + } else { + // Check that authorization requests come with an acceptable callback URI. + var authorizationRequest = message as EndUserAuthorizationRequest; + if (authorizationRequest != null) { + var client = this.AuthorizationServer.GetClientOrThrow(authorizationRequest.ClientIdentifier); + ErrorUtilities.VerifyProtocol(authorizationRequest.Callback == null || client.IsCallbackAllowed(authorizationRequest.Callback), AuthServerStrings.ClientCallbackDisallowed, authorizationRequest.Callback); + ErrorUtilities.VerifyProtocol(authorizationRequest.Callback != null || client.DefaultCallback != null, AuthServerStrings.NoCallback); + applied = true; + } + + // Check that the callback URI in a direct message from the client matches the one in the indirect message received earlier. + var request = message as AccessTokenAuthorizationCodeRequestAS; + if (request != null) { + IAuthorizationCodeCarryingRequest tokenRequest = request; + tokenRequest.AuthorizationDescription.VerifyCallback(request.Callback); + applied = true; + } + + var authCarrier = message as IAuthorizationCarryingRequest; + if (authCarrier != null) { + var accessRequest = authCarrier as AccessTokenRequestBase; + if (accessRequest != null) { + // Make sure the client sending us this token is the client we issued the token to. + AuthServerUtilities.TokenEndpointVerify(string.Equals(accessRequest.ClientIdentifier, authCarrier.AuthorizationDescription.ClientIdentifier, StringComparison.Ordinal), accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.InvalidClient); + + var scopedAccessRequest = accessRequest as ScopedAccessTokenRequest; + if (scopedAccessRequest != null) { + // Make sure the scope the client is requesting does not exceed the scope in the grant. + if (!this.AuthServerChannel.ScopeSatisfiedCheck.IsScopeSatisfied(requiredScope: scopedAccessRequest.Scope, grantedScope: authCarrier.AuthorizationDescription.Scope)) { + Logger.OAuth.ErrorFormat("The requested access scope (\"{0}\") exceeds the grant scope (\"{1}\").", scopedAccessRequest.Scope, authCarrier.AuthorizationDescription.Scope); + throw new TokenEndpointProtocolException(accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.InvalidScope, AuthServerStrings.AccessScopeExceedsGrantScope); + } + } + } + + // Make sure the authorization this token represents hasn't already been revoked. + if (!this.AuthorizationServer.IsAuthorizationValid(authCarrier.AuthorizationDescription)) { + Logger.OAuth.Error("Rejecting access token request because the IAuthorizationServerHost.IsAuthorizationValid method returned false."); + throw new TokenEndpointProtocolException(accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.InvalidGrant); + } + + applied = true; + } + } + + return applied ? (MessageProtections?)MessageProtections.None : null; + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs new file mode 100644 index 0000000..7ca4538 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs @@ -0,0 +1,132 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuth2AuthorizationServerChannel.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.Net.Mime; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth2.AuthServer.Messages; + using DotNetOpenAuth.OAuth2.Messages; + + /// <summary> + /// The channel for the OAuth protocol. + /// </summary> + internal class OAuth2AuthorizationServerChannel : OAuth2ChannelBase, IOAuth2ChannelWithAuthorizationServer { + /// <summary> + /// The messages receivable by this channel. + /// </summary> + private static readonly Type[] MessageTypes = new Type[] { + typeof(AccessTokenRefreshRequestAS), + typeof(AccessTokenAuthorizationCodeRequestAS), + typeof(AccessTokenResourceOwnerPasswordCredentialsRequest), + typeof(AccessTokenClientCredentialsRequest), + typeof(EndUserAuthorizationRequest), + typeof(EndUserAuthorizationImplicitRequest), + typeof(EndUserAuthorizationFailedResponse), + }; + + /// <summary> + /// Initializes a new instance of the <see cref="OAuth2AuthorizationServerChannel"/> class. + /// </summary> + /// <param name="authorizationServer">The authorization server.</param> + /// <param name="clientAuthenticationModule">The aggregating client authentication module.</param> + protected internal OAuth2AuthorizationServerChannel(IAuthorizationServerHost authorizationServer, ClientAuthenticationModule clientAuthenticationModule) + : base(MessageTypes, InitializeBindingElements(authorizationServer, clientAuthenticationModule)) { + Requires.NotNull(authorizationServer, "authorizationServer"); + this.AuthorizationServer = authorizationServer; + } + + /// <summary> + /// Gets the authorization server. + /// </summary> + /// <value>The authorization server.</value> + public IAuthorizationServerHost AuthorizationServer { get; private set; } + + /// <summary> + /// Gets or sets the service that checks whether a granted set of scopes satisfies a required set of scopes. + /// </summary> + public IScopeSatisfiedCheck ScopeSatisfiedCheck { get; set; } + + /// <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) { + throw new NotImplementedException(); + } + + /// <summary> + /// Queues a message for sending in the response stream. + /// </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(); + ApplyMessageTemplate(response, webResponse); + string json = this.SerializeAsJson(response); + webResponse.SetResponse(json, new ContentType(JsonEncoded)); + return webResponse; + } + + /// <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) { + if (!string.IsNullOrEmpty(request.Url.Fragment)) { + var fields = HttpUtility.ParseQueryString(request.Url.Fragment.Substring(1)).ToDictionary(); + + MessageReceivingEndpoint recipient; + try { + recipient = request.GetRecipient(); + } catch (ArgumentException ex) { + Logger.Messaging.WarnFormat("Unrecognized HTTP request: " + ex.ToString()); + return null; + } + + return (IDirectedProtocolMessage)this.Receive(fields, recipient); + } + + return base.ReadFromRequestCore(request); + } + + /// <summary> + /// Initializes the binding elements for the OAuth channel. + /// </summary> + /// <param name="authorizationServer">The authorization server.</param> + /// <param name="clientAuthenticationModule">The aggregating client authentication module.</param> + /// <returns> + /// An array of binding elements used to initialize the channel. + /// </returns> + private static IChannelBindingElement[] InitializeBindingElements(IAuthorizationServerHost authorizationServer, ClientAuthenticationModule clientAuthenticationModule) { + Requires.NotNull(authorizationServer, "authorizationServer"); + Requires.NotNull(clientAuthenticationModule, "clientAuthenticationModule"); + + var bindingElements = new List<IChannelBindingElement>(); + + // The order they are provided is used for outgoing messgaes, and reversed for incoming messages. + bindingElements.Add(new MessageValidationBindingElement(clientAuthenticationModule)); + bindingElements.Add(new TokenCodeSerializationBindingElement()); + + return bindingElements.ToArray(); + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/RefreshToken.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/RefreshToken.cs new file mode 100644 index 0000000..993583c --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/RefreshToken.cs @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------- +// <copyright file="RefreshToken.cs" company="Outercurve Foundation"> +// Copyright (c) Outercurve Foundation. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.ChannelElements { + using System; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + + /// <summary> + /// The refresh token issued to a client by an authorization server that allows the client + /// to periodically obtain new short-lived access tokens. + /// </summary> + internal class RefreshToken : AuthorizationDataBag { + /// <summary> + /// The name of the bucket for symmetric keys used to sign refresh tokens. + /// </summary> + internal const string RefreshTokenKeyBucket = "https://localhost/dnoa/oauth_refresh_token"; + + /// <summary> + /// Initializes a new instance of the <see cref="RefreshToken"/> class. + /// </summary> + public RefreshToken() { + } + + /// <summary> + /// Initializes a new instance of the <see cref="RefreshToken"/> class. + /// </summary> + /// <param name="authorization">The authorization this refresh token should describe.</param> + internal RefreshToken(IAuthorizationDescription authorization) { + Requires.NotNull(authorization, "authorization"); + + this.ClientIdentifier = authorization.ClientIdentifier; + this.UtcCreationDate = authorization.UtcIssued; + this.User = authorization.User; + this.Scope.ResetContents(authorization.Scope); + } + + /// <summary> + /// Creates a formatter capable of serializing/deserializing a refresh token. + /// </summary> + /// <param name="cryptoKeyStore">The crypto key store.</param> + /// <returns> + /// A DataBag formatter. Never null. + /// </returns> + internal static IDataBagFormatter<RefreshToken> CreateFormatter(ICryptoKeyStore cryptoKeyStore) { + Requires.NotNull(cryptoKeyStore, "cryptoKeyStore"); + Contract.Ensures(Contract.Result<IDataBagFormatter<RefreshToken>>() != null); + + return new UriStyleMessageFormatter<RefreshToken>(cryptoKeyStore, RefreshTokenKeyBucket, signed: true, encrypted: true); + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/TokenCodeSerializationBindingElement.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/TokenCodeSerializationBindingElement.cs new file mode 100644 index 0000000..494a10b --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/TokenCodeSerializationBindingElement.cs @@ -0,0 +1,121 @@ +//----------------------------------------------------------------------- +// <copyright file="TokenCodeSerializationBindingElement.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.CodeAnalysis; + using System.Globalization; + using System.Linq; + using System.Security.Cryptography; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.OAuth2.AuthServer.ChannelElements; + using DotNetOpenAuth.OAuth2.Messages; + + /// <summary> + /// Serializes and deserializes authorization codes, refresh tokens and access tokens + /// on incoming and outgoing messages. + /// </summary> + internal class TokenCodeSerializationBindingElement : AuthServerBindingElementBase { + /// <summary> + /// Gets the protection commonly offered (if any) by this binding element. + /// </summary> + /// <value></value> + /// <remarks> + /// This value is used to assist in sorting binding elements in the channel stack. + /// </remarks> + public override MessageProtections Protection { + get { return MessageProtections.None; } + } + + /// <summary> + /// Prepares a message for sending based on the rules of this channel binding element. + /// </summary> + /// <param name="message">The message to prepare for sending.</param> + /// <returns> + /// The protections (if any) that this binding element applied to the message. + /// Null if this binding element did not even apply to this binding element. + /// </returns> + /// <remarks> + /// Implementations that provide message protection must honor the + /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable. + /// </remarks> + public override MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { + var directResponse = message as IDirectResponseProtocolMessage; + var request = directResponse != null ? directResponse.OriginatingRequest as IAccessTokenRequestInternal : null; + + // Serialize the authorization code, if there is one. + var authCodeCarrier = message as IAuthorizationCodeCarryingRequest; + if (authCodeCarrier != null) { + var codeFormatter = AuthorizationCode.CreateFormatter(this.AuthorizationServer); + var code = authCodeCarrier.AuthorizationDescription; + authCodeCarrier.Code = codeFormatter.Serialize(code); + return MessageProtections.None; + } + + // Serialize the refresh token, if applicable. + var refreshTokenResponse = message as AccessTokenSuccessResponse; + if (refreshTokenResponse != null && refreshTokenResponse.HasRefreshToken) { + var refreshTokenCarrier = (IAuthorizationCarryingRequest)message; + var refreshToken = new RefreshToken(refreshTokenCarrier.AuthorizationDescription); + var refreshTokenFormatter = RefreshToken.CreateFormatter(this.AuthorizationServer.CryptoKeyStore); + refreshTokenResponse.RefreshToken = refreshTokenFormatter.Serialize(refreshToken); + } + + // Serialize the access token, if applicable. + var accessTokenResponse = message as IAccessTokenIssuingResponse; + if (accessTokenResponse != null && accessTokenResponse.AuthorizationDescription != null) { + ErrorUtilities.VerifyInternal(request != null, "We should always have a direct request message for this case."); + accessTokenResponse.AccessToken = accessTokenResponse.AuthorizationDescription.Serialize(); + } + + return null; + } + + /// <summary> + /// Performs any transformation on an incoming message that may be necessary and/or + /// validates an incoming message based on the rules of this channel binding element. + /// </summary> + /// <param name="message">The incoming message to process.</param> + /// <returns> + /// The protections (if any) that this binding element applied to the message. + /// Null if this binding element did not even apply to this binding element. + /// </returns> + /// <exception cref="ProtocolException"> + /// Thrown when the binding element rules indicate that this message is invalid and should + /// NOT be processed. + /// </exception> + /// <remarks> + /// Implementations that provide message protection must honor the + /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable. + /// </remarks> + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "unauthorizedclient", Justification = "Protocol requirement")] + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "incorrectclientcredentials", Justification = "Protocol requirement")] + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "authorizationexpired", Justification = "Protocol requirement")] + [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "DotNetOpenAuth.Messaging.ErrorUtilities.VerifyProtocol(System.Boolean,System.String,System.Object[])", Justification = "Protocol requirement")] + public override MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { + var authCodeCarrier = message as IAuthorizationCodeCarryingRequest; + if (authCodeCarrier != null) { + var authorizationCodeFormatter = AuthorizationCode.CreateFormatter(this.AuthorizationServer); + var authorizationCode = new AuthorizationCode(); + authorizationCodeFormatter.Deserialize(authorizationCode, message, authCodeCarrier.Code, Protocol.code); + authCodeCarrier.AuthorizationDescription = authorizationCode; + } + + var refreshTokenCarrier = message as IRefreshTokenCarryingRequest; + if (refreshTokenCarrier != null) { + var refreshTokenFormatter = RefreshToken.CreateFormatter(this.AuthorizationServer.CryptoKeyStore); + var refreshToken = new RefreshToken(); + refreshTokenFormatter.Deserialize(refreshToken, message, refreshTokenCarrier.RefreshToken, Protocol.refresh_token); + refreshTokenCarrier.AuthorizationDescription = refreshToken; + } + + return null; + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ClientDescription.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ClientDescription.cs new file mode 100644 index 0000000..3384183 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ClientDescription.cs @@ -0,0 +1,90 @@ +//----------------------------------------------------------------------- +// <copyright file="ClientDescription.cs" company="Outercurve Foundation"> +// Copyright (c) Outercurve Foundation. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2 { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A default implementation of the <see cref="IClientDescription"/> interface. + /// </summary> + public class ClientDescription : IClientDescription { + /// <summary> + /// The client's secret, if any. + /// </summary> + private readonly string secret; + + /// <summary> + /// Initializes a new instance of the <see cref="ClientDescription"/> class. + /// </summary> + /// <param name="secret">The secret.</param> + /// <param name="defaultCallback">The default callback.</param> + /// <param name="clientType">Type of the client.</param> + public ClientDescription(string secret, Uri defaultCallback, ClientType clientType) { + this.secret = secret; + this.DefaultCallback = defaultCallback; + this.ClientType = clientType; + } + + #region IClientDescription Members + + /// <summary> + /// Gets the callback to use when an individual authorization request + /// does not include an explicit callback URI. + /// </summary> + /// <value> + /// An absolute URL; or <c>null</c> if none is registered. + /// </value> + public Uri DefaultCallback { get; private set; } + + /// <summary> + /// Gets the type of the client. + /// </summary> + public ClientType ClientType { get; private set; } + + /// <summary> + /// Gets a value indicating whether a non-empty secret is registered for this client. + /// </summary> + public virtual bool HasNonEmptySecret { + get { return !string.IsNullOrEmpty(this.secret); } + } + + /// <summary> + /// Determines whether a callback URI included in a client's authorization request + /// is among those allowed callbacks for the registered client. + /// </summary> + /// <param name="callback">The absolute URI the client has requested the authorization result be received at. Never null.</param> + /// <returns> + /// <c>true</c> if the callback URL is allowable for this client; otherwise, <c>false</c>. + /// </returns> + /// <remarks> + /// This method may be overridden to allow for several callbacks to match. + /// </remarks> + public virtual bool IsCallbackAllowed(Uri callback) { + return EqualityComparer<Uri>.Default.Equals(this.DefaultCallback, callback); + } + + /// <summary> + /// Checks whether the specified client secret is correct. + /// </summary> + /// <param name="secret">The secret obtained from the client.</param> + /// <returns><c>true</c> if the secret matches the one in the authorization server's record for the client; <c>false</c> otherwise.</returns> + /// <remarks> + /// All string equality checks, whether checking secrets or their hashes, + /// should be done using <see cref="MessagingUtilities.EqualsConstantTime"/> to mitigate timing attacks. + /// </remarks> + public virtual bool IsValidClientSecret(string secret) { + Requires.NotNullOrEmpty(secret, "secret"); + + return MessagingUtilities.EqualsConstantTime(secret, this.secret); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/IAuthorizationServerHost.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/IAuthorizationServerHost.cs new file mode 100644 index 0000000..b75cb29 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/IAuthorizationServerHost.cs @@ -0,0 +1,259 @@ +//----------------------------------------------------------------------- +// <copyright file="IAuthorizationServerHost.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.Linq; + using System.Security.Cryptography; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.OAuth2.ChannelElements; + using DotNetOpenAuth.OAuth2.Messages; + + /// <summary> + /// Provides host-specific authorization server services needed by this library. + /// </summary> + [ContractClass(typeof(IAuthorizationServerHostContract))] + public interface IAuthorizationServerHost { + /// <summary> + /// Gets the store for storing crypto keys used to symmetrically encrypt and sign authorization codes and refresh tokens. + /// </summary> + /// <remarks> + /// This store should be kept strictly confidential in the authorization server(s) + /// and NOT shared with the resource server. Anyone with these secrets can mint + /// tokens to essentially grant themselves access to anything they want. + /// </remarks> + ICryptoKeyStore CryptoKeyStore { get; } + + /// <summary> + /// Gets the authorization code nonce store to use to ensure that authorization codes can only be used once. + /// </summary> + /// <value>The authorization code nonce store.</value> + INonceStore NonceStore { get; } + + /// <summary> + /// Acquires the access token and related parameters that go into the formulation of the token endpoint's response to a client. + /// </summary> + /// <param name="accessTokenRequestMessage">Details regarding the resources that the access token will grant access to, and the identity of the client + /// that will receive that access. + /// Based on this information the receiving resource server can be determined and the lifetime of the access + /// token can be set based on the sensitivity of the resources. + /// </param> + /// <returns>A non-null parameters instance that DotNetOpenAuth will dispose after it has been used.</returns> + AccessTokenResult CreateAccessToken(IAccessTokenRequest accessTokenRequestMessage); + + /// <summary> + /// Gets the client with a given identifier. + /// </summary> + /// <param name="clientIdentifier">The client identifier.</param> + /// <returns>The client registration. Never null.</returns> + /// <exception cref="ArgumentException">Thrown when no client with the given identifier is registered with this authorization server.</exception> + IClientDescription GetClient(string clientIdentifier); + + /// <summary> + /// Determines whether a described authorization is (still) valid. + /// </summary> + /// <param name="authorization">The authorization.</param> + /// <returns> + /// <c>true</c> if the original authorization is still valid; otherwise, <c>false</c>. + /// </returns> + /// <remarks> + /// <para>When establishing that an authorization is still valid, + /// it's very important to only match on recorded authorizations that + /// meet these criteria:</para> + /// 1) The client identifier matches. + /// 2) The user account matches. + /// 3) The scope on the recorded authorization must include all scopes in the given authorization. + /// 4) The date the recorded authorization was issued must be <em>no later</em> that the date the given authorization was issued. + /// <para>One possible scenario is where the user authorized a client, later revoked authorization, + /// and even later reinstated authorization. This subsequent recorded authorization + /// would not satisfy requirement #4 in the above list. This is important because the revocation + /// the user went through should invalidate all previously issued tokens as a matter of + /// security in the event the user was revoking access in order to sever authorization on a stolen + /// account or piece of hardware in which the tokens were stored. </para> + /// </remarks> + bool IsAuthorizationValid(IAuthorizationDescription authorization); + + /// <summary> + /// Determines whether a given set of resource owner credentials is valid based on the authorization server's user database + /// and if so records an authorization entry such that subsequent calls to <see cref="IsAuthorizationValid"/> would + /// return <c>true</c>. + /// </summary> + /// <param name="userName">Username on the account.</param> + /// <param name="password">The user's password.</param> + /// <param name="accessRequest"> + /// The access request the credentials came with. + /// This may be useful if the authorization server wishes to apply some policy based on the client that is making the request. + /// </param> + /// <param name="canonicalUserName"> + /// Receives the canonical username (normalized for the resource server) of the user, for valid credentials; + /// Or <c>null</c> if the return value is false. + /// </param> + /// <returns> + /// <c>true</c> if the given credentials are valid and the authorization granted; otherwise, <c>false</c>. + /// </returns> + /// <exception cref="NotSupportedException"> + /// May be thrown if the authorization server does not support the resource owner password credential grant type. + /// </exception> + bool TryAuthorizeResourceOwnerCredentialGrant(string userName, string password, IAccessTokenRequest accessRequest, out string canonicalUserName); + + /// <summary> + /// Determines whether an access token request given a client credential grant should be authorized + /// and if so records an authorization entry such that subsequent calls to <see cref="IsAuthorizationValid"/> would + /// return <c>true</c>. + /// </summary> + /// <param name="accessRequest"> + /// The access request the credentials came with. + /// This may be useful if the authorization server wishes to apply some policy based on the client that is making the request. + /// </param> + /// <returns> + /// <c>true</c> if the given credentials are valid and the authorization granted; otherwise, <c>false</c>. + /// </returns> + /// <exception cref="NotSupportedException"> + /// May be thrown if the authorization server does not support the client credential grant type. + /// </exception> + bool TryAuthorizeClientCredentialsGrant(IAccessTokenRequest accessRequest); + } + + /// <summary> + /// Code Contract for the <see cref="IAuthorizationServerHost"/> interface. + /// </summary> + [ContractClassFor(typeof(IAuthorizationServerHost))] + internal abstract class IAuthorizationServerHostContract : IAuthorizationServerHost { + /// <summary> + /// Prevents a default instance of the <see cref="IAuthorizationServerHostContract"/> class from being created. + /// </summary> + private IAuthorizationServerHostContract() { + } + + /// <summary> + /// Gets the store for storeing crypto keys used to symmetrically encrypt and sign authorization codes and refresh tokens. + /// </summary> + ICryptoKeyStore IAuthorizationServerHost.CryptoKeyStore { + get { + Contract.Ensures(Contract.Result<ICryptoKeyStore>() != null); + throw new NotImplementedException(); + } + } + + /// <summary> + /// Gets the authorization code nonce store to use to ensure that authorization codes can only be used once. + /// </summary> + /// <value>The authorization code nonce store.</value> + INonceStore IAuthorizationServerHost.NonceStore { + get { + Contract.Ensures(Contract.Result<INonceStore>() != null); + throw new NotImplementedException(); + } + } + + /// <summary> + /// Gets the client with a given identifier. + /// </summary> + /// <param name="clientIdentifier">The client identifier.</param> + /// <returns>The client registration. Never null.</returns> + /// <exception cref="ArgumentException">Thrown when no client with the given identifier is registered with this authorization server.</exception> + IClientDescription IAuthorizationServerHost.GetClient(string clientIdentifier) { + Requires.NotNullOrEmpty(clientIdentifier, "clientIdentifier"); + Contract.Ensures(Contract.Result<IClientDescription>() != null); + throw new NotImplementedException(); + } + + /// <summary> + /// Determines whether a described authorization is (still) valid. + /// </summary> + /// <param name="authorization">The authorization.</param> + /// <returns> + /// <c>true</c> if the original authorization is still valid; otherwise, <c>false</c>. + /// </returns> + /// <remarks> + /// <para>When establishing that an authorization is still valid, + /// it's very important to only match on recorded authorizations that + /// meet these criteria:</para> + /// 1) The client identifier matches. + /// 2) The user account matches. + /// 3) The scope on the recorded authorization must include all scopes in the given authorization. + /// 4) The date the recorded authorization was issued must be <em>no later</em> that the date the given authorization was issued. + /// <para>One possible scenario is where the user authorized a client, later revoked authorization, + /// and even later reinstated authorization. This subsequent recorded authorization + /// would not satisfy requirement #4 in the above list. This is important because the revocation + /// the user went through should invalidate all previously issued tokens as a matter of + /// security in the event the user was revoking access in order to sever authorization on a stolen + /// account or piece of hardware in which the tokens were stored. </para> + /// </remarks> + bool IAuthorizationServerHost.IsAuthorizationValid(IAuthorizationDescription authorization) { + Requires.NotNull(authorization, "authorization"); + throw new NotImplementedException(); + } + + /// <summary> + /// Determines whether a given set of resource owner credentials is valid based on the authorization server's user database + /// and if so records an authorization entry such that subsequent calls to <see cref="IAuthorizationServerHost.IsAuthorizationValid"/> would + /// return <c>true</c>. + /// </summary> + /// <param name="userName">Username on the account.</param> + /// <param name="password">The user's password.</param> + /// <param name="accessRequest"> + /// The access request the credentials came with. + /// This may be useful if the authorization server wishes to apply some policy based on the client that is making the request. + /// </param> + /// <param name="canonicalUserName"> + /// Receives the canonical username (normalized for the resource server) of the user, for valid credentials; + /// Or <c>null</c> if the return value is false. + /// </param> + /// <returns> + /// <c>true</c> if the given credentials are valid and the authorization granted; otherwise, <c>false</c>. + /// </returns> + /// <exception cref="NotSupportedException"> + /// May be thrown if the authorization server does not support the resource owner password credential grant type. + /// </exception> + bool IAuthorizationServerHost.TryAuthorizeResourceOwnerCredentialGrant(string userName, string password, IAccessTokenRequest accessRequest, out string canonicalUserName) { + Contract.Requires(!string.IsNullOrEmpty(userName)); + Contract.Requires(password != null); + Contract.Requires(accessRequest != null); + Contract.Ensures(!Contract.Result<bool>() || !string.IsNullOrEmpty(Contract.ValueAtReturn<string>(out canonicalUserName))); + throw new NotImplementedException(); + } + + /// <summary> + /// Determines whether an access token request given a client credential grant should be authorized + /// and if so records an authorization entry such that subsequent calls to <see cref="IAuthorizationServerHost.IsAuthorizationValid"/> would + /// return <c>true</c>. + /// </summary> + /// <param name="accessRequest"> + /// The access request the credentials came with. + /// This may be useful if the authorization server wishes to apply some policy based on the client that is making the request. + /// </param> + /// <returns> + /// <c>true</c> if the given credentials are valid and the authorization granted; otherwise, <c>false</c>. + /// </returns> + /// <exception cref="NotSupportedException"> + /// May be thrown if the authorization server does not support the client credential grant type. + /// </exception> + bool IAuthorizationServerHost.TryAuthorizeClientCredentialsGrant(IAccessTokenRequest accessRequest) { + throw new NotImplementedException(); + } + + /// <summary> + /// Obtains parameters to go into the formulation of an access token. + /// </summary> + /// <param name="accessTokenRequestMessage">Details regarding the resources that the access token will grant access to, and the identity of the client + /// that will receive that access. + /// Based on this information the receiving resource server can be determined and the lifetime of the access + /// token can be set based on the sensitivity of the resources.</param> + /// <returns> + /// A non-null parameters instance that DotNetOpenAuth will dispose after it has been used. + /// </returns> + AccessTokenResult IAuthorizationServerHost.CreateAccessToken(IAccessTokenRequest accessTokenRequestMessage) { + Contract.Requires(accessTokenRequestMessage != null); + Contract.Ensures(Contract.Result<AccessTokenResult>() != null); + throw new NotImplementedException(); + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/AccessTokenAuthorizationCodeRequestAS.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/AccessTokenAuthorizationCodeRequestAS.cs new file mode 100644 index 0000000..ca14d0e --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/AccessTokenAuthorizationCodeRequestAS.cs @@ -0,0 +1,53 @@ +//----------------------------------------------------------------------- +// <copyright file="AccessTokenAuthorizationCodeRequestAS.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.Messages { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.OAuth2.ChannelElements; + + /// <summary> + /// A request from a Client to an Authorization Server to exchange an authorization code for an access token, + /// and (at the authorization server's option) a refresh token. + /// </summary> + internal class AccessTokenAuthorizationCodeRequestAS : AccessTokenAuthorizationCodeRequest, IAuthorizationCodeCarryingRequest { + /// <summary> + /// Initializes a new instance of the <see cref="AccessTokenAuthorizationCodeRequestAS"/> class. + /// </summary> + /// <param name="tokenEndpoint">The Authorization Server's access token endpoint URL.</param> + /// <param name="version">The version.</param> + internal AccessTokenAuthorizationCodeRequestAS(Uri tokenEndpoint, Version version) + : base(tokenEndpoint, version) { + } + + #region IAuthorizationCodeCarryingRequest Members + + /// <summary> + /// Gets or sets the verification code or refresh/access token. + /// </summary> + /// <value>The code or token.</value> + string IAuthorizationCodeCarryingRequest.Code { + get { return this.AuthorizationCode; } + set { this.AuthorizationCode = value; } + } + + /// <summary> + /// Gets or sets the authorization that the token describes. + /// </summary> + AuthorizationCode IAuthorizationCodeCarryingRequest.AuthorizationDescription { get; set; } + + /// <summary> + /// Gets the authorization that the code describes. + /// </summary> + IAuthorizationDescription IAuthorizationCarryingRequest.AuthorizationDescription { + get { return ((IAuthorizationCodeCarryingRequest)this).AuthorizationDescription; } + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/AccessTokenRefreshRequestAS.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/AccessTokenRefreshRequestAS.cs new file mode 100644 index 0000000..d9ca4c8 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/AccessTokenRefreshRequestAS.cs @@ -0,0 +1,55 @@ +//----------------------------------------------------------------------- +// <copyright file="AccessTokenRefreshRequestAS.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.AuthServer.Messages { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.OAuth2.AuthServer.ChannelElements; + using DotNetOpenAuth.OAuth2.ChannelElements; + using DotNetOpenAuth.OAuth2.Messages; + + /// <summary> + /// A request from the client to the token endpoint for a new access token + /// in exchange for a refresh token that the client has previously obtained. + /// </summary> + internal class AccessTokenRefreshRequestAS : AccessTokenRefreshRequest, IRefreshTokenCarryingRequest { + /// <summary> + /// Initializes a new instance of the <see cref="AccessTokenRefreshRequestAS"/> class. + /// </summary> + /// <param name="tokenEndpoint">The token endpoint.</param> + /// <param name="version">The version.</param> + internal AccessTokenRefreshRequestAS(Uri tokenEndpoint, Version version) + : base(tokenEndpoint, version) { + } + + #region IRefreshTokenCarryingRequest members + + /// <summary> + /// Gets or sets the verification code or refresh/access token. + /// </summary> + /// <value>The code or token.</value> + string IRefreshTokenCarryingRequest.RefreshToken { + get { return this.RefreshToken; } + set { this.RefreshToken = value; } + } + + /// <summary> + /// Gets or sets the authorization that the token describes. + /// </summary> + RefreshToken IRefreshTokenCarryingRequest.AuthorizationDescription { get; set; } + + /// <summary> + /// Gets the authorization that the token describes. + /// </summary> + IAuthorizationDescription IAuthorizationCarryingRequest.AuthorizationDescription { + get { return ((IRefreshTokenCarryingRequest)this).AuthorizationDescription; } + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/EndUserAuthorizationSuccessAuthCodeResponseAS.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/EndUserAuthorizationSuccessAuthCodeResponseAS.cs new file mode 100644 index 0000000..25f5dc8 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/EndUserAuthorizationSuccessAuthCodeResponseAS.cs @@ -0,0 +1,67 @@ +//----------------------------------------------------------------------- +// <copyright file="EndUserAuthorizationSuccessAuthCodeResponseAS.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.Messages { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.OAuth2.ChannelElements; + + /// <summary> + /// The message sent by the Authorization Server to the Client via the user agent + /// to indicate that user authorization was granted, carrying an authorization code and possibly an access token, + /// and to return the user to the Client where they started their experience. + /// </summary> + internal class EndUserAuthorizationSuccessAuthCodeResponseAS : EndUserAuthorizationSuccessAuthCodeResponse, IAuthorizationCodeCarryingRequest { + /// <summary> + /// Initializes a new instance of the <see cref="EndUserAuthorizationSuccessAuthCodeResponseAS"/> class. + /// </summary> + /// <param name="clientCallback">The URL to redirect to so the client receives the message. This may not be built into the request message if the client pre-registered the URL with the authorization server.</param> + /// <param name="version">The protocol version.</param> + internal EndUserAuthorizationSuccessAuthCodeResponseAS(Uri clientCallback, Version version) + : base(clientCallback, version) { + Requires.NotNull(version, "version"); + Requires.NotNull(clientCallback, "clientCallback"); + } + + /// <summary> + /// Initializes a new instance of the <see cref="EndUserAuthorizationSuccessAuthCodeResponseAS"/> class. + /// </summary> + /// <param name="clientCallback">The URL to redirect to so the client receives the message. This may not be built into the request message if the client pre-registered the URL with the authorization server.</param> + /// <param name="request">The authorization request from the user agent on behalf of the client.</param> + internal EndUserAuthorizationSuccessAuthCodeResponseAS(Uri clientCallback, EndUserAuthorizationRequest request) + : base(clientCallback, request) { + Requires.NotNull(clientCallback, "clientCallback"); + Requires.NotNull(request, "request"); + ((IMessageWithClientState)this).ClientState = request.ClientState; + } + + #region IAuthorizationCodeCarryingRequest Members + + /// <summary> + /// Gets or sets the authorization code. + /// </summary> + string IAuthorizationCodeCarryingRequest.Code { + get { return this.AuthorizationCode; } + set { this.AuthorizationCode = value; } + } + + /// <summary> + /// Gets or sets the authorization that the token describes. + /// </summary> + AuthorizationCode IAuthorizationCodeCarryingRequest.AuthorizationDescription { get; set; } + + /// <summary> + /// Gets the authorization that the code describes. + /// </summary> + IAuthorizationDescription IAuthorizationCarryingRequest.AuthorizationDescription { + get { return ((IAuthorizationCodeCarryingRequest)this).AuthorizationDescription; } + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/IAuthorizationCodeCarryingRequest.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/IAuthorizationCodeCarryingRequest.cs new file mode 100644 index 0000000..045cb80 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/IAuthorizationCodeCarryingRequest.cs @@ -0,0 +1,22 @@ +//----------------------------------------------------------------------- +// <copyright file="IAuthorizationCodeCarryingRequest.cs" company="Outercurve Foundation"> +// Copyright (c) Outercurve Foundation. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.ChannelElements { + /// <summary> + /// A message that carries an authorization code between client and authorization server. + /// </summary> + internal interface IAuthorizationCodeCarryingRequest : IAuthorizationCarryingRequest { + /// <summary> + /// Gets or sets the authorization code. + /// </summary> + string Code { get; set; } + + /// <summary> + /// Gets or sets the authorization that the code describes. + /// </summary> + new AuthorizationCode AuthorizationDescription { get; set; } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/IRefreshTokenCarryingRequest.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/IRefreshTokenCarryingRequest.cs new file mode 100644 index 0000000..9e6fc3c --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/IRefreshTokenCarryingRequest.cs @@ -0,0 +1,24 @@ +//----------------------------------------------------------------------- +// <copyright file="IRefreshTokenCarryingRequest.cs" company="Outercurve Foundation"> +// Copyright (c) Outercurve Foundation. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.AuthServer.ChannelElements { + using DotNetOpenAuth.OAuth2.ChannelElements; + + /// <summary> + /// A message that carries a refresh token between client and authorization server. + /// </summary> + internal interface IRefreshTokenCarryingRequest : IAuthorizationCarryingRequest { + /// <summary> + /// Gets or sets the refresh token. + /// </summary> + string RefreshToken { get; set; } + + /// <summary> + /// Gets or sets the authorization that the token describes. + /// </summary> + new RefreshToken AuthorizationDescription { get; set; } + } +} |