summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.OAuth2.AuthorizationServer
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenAuth.OAuth2.AuthorizationServer')
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/Configuration/OAuth2AuthorizationServerSection.cs70
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/DotNetOpenAuth.OAuth2.AuthorizationServer.csproj36
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerStrings.Designer.cs117
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerStrings.resx138
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerUtilities.cs65
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs112
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServerAccessToken.cs52
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AggregatingClientCredentialReader.cs91
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AuthServerBindingElementBase.cs88
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AuthorizationCode.cs118
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientAuthenticationModule.cs74
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialHttpBasicReader.cs48
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialMessagePartReader.cs34
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/IOAuth2ChannelWithAuthorizationServer.cs24
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/MessageValidationBindingElement.cs202
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs132
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/RefreshToken.cs56
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/TokenCodeSerializationBindingElement.cs121
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ClientDescription.cs90
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/IAuthorizationServerHost.cs259
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/AccessTokenAuthorizationCodeRequestAS.cs53
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/AccessTokenRefreshRequestAS.cs55
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/EndUserAuthorizationSuccessAuthCodeResponseAS.cs67
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/IAuthorizationCodeCarryingRequest.cs22
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/IRefreshTokenCarryingRequest.cs24
25 files changed, 2124 insertions, 24 deletions
diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/Configuration/OAuth2AuthorizationServerSection.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/Configuration/OAuth2AuthorizationServerSection.cs
new file mode 100644
index 0000000..6511a11
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/Configuration/OAuth2AuthorizationServerSection.cs
@@ -0,0 +1,70 @@
+//-----------------------------------------------------------------------
+// <copyright file="OAuth2AuthorizationServerSection.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Configuration {
+ using System;
+ using System.Configuration;
+ using System.Diagnostics.Contracts;
+ using DotNetOpenAuth.Messaging.Bindings;
+ using DotNetOpenAuth.OAuth2.ChannelElements;
+
+ /// <summary>
+ /// Represents the &lt;oauth2/authorizationServer&gt; section in the host's .config file.
+ /// </summary>
+ internal class OAuth2AuthorizationServerSection : ConfigurationSection {
+ /// <summary>
+ /// The name of the oauth2/authorizationServer section.
+ /// </summary>
+ private const string SectionName = OAuth2SectionGroup.SectionName + "/authorizationServer";
+
+ /// <summary>
+ /// The name of the &lt;clientAuthenticationModules&gt; sub-element.
+ /// </summary>
+ private const string ClientAuthenticationModulesElementName = "clientAuthenticationModules";
+
+ /// <summary>
+ /// The built-in set of client authentication modules.
+ /// </summary>
+ private static readonly TypeConfigurationCollection<ClientAuthenticationModule> defaultClientAuthenticationModules =
+ new TypeConfigurationCollection<ClientAuthenticationModule>(new Type[] { typeof(ClientCredentialHttpBasicReader), typeof(ClientCredentialMessagePartReader) });
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OAuth2AuthorizationServerSection"/> class.
+ /// </summary>
+ internal OAuth2AuthorizationServerSection() {
+ }
+
+ /// <summary>
+ /// Gets the configuration section from the .config file.
+ /// </summary>
+ internal static OAuth2AuthorizationServerSection Configuration {
+ get {
+ Contract.Ensures(Contract.Result<OAuth2AuthorizationServerSection>() != null);
+ return (OAuth2AuthorizationServerSection)ConfigurationManager.GetSection(SectionName) ?? new OAuth2AuthorizationServerSection();
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the services to use for discovering service endpoints for identifiers.
+ /// </summary>
+ /// <remarks>
+ /// If no discovery services are defined in the (web) application's .config file,
+ /// the default set of discovery services built into the library are used.
+ /// </remarks>
+ [ConfigurationProperty(ClientAuthenticationModulesElementName, IsDefaultCollection = false)]
+ [ConfigurationCollection(typeof(TypeConfigurationCollection<ClientAuthenticationModule>))]
+ internal TypeConfigurationCollection<ClientAuthenticationModule> ClientAuthenticationModules {
+ get {
+ var configResult = (TypeConfigurationCollection<ClientAuthenticationModule>)this[ClientAuthenticationModulesElementName];
+ return configResult != null && configResult.Count > 0 ? configResult : defaultClientAuthenticationModules;
+ }
+
+ set {
+ this[ClientAuthenticationModulesElementName] = value;
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/DotNetOpenAuth.OAuth2.AuthorizationServer.csproj b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/DotNetOpenAuth.OAuth2.AuthorizationServer.csproj
index f6b1a50..34d59ee 100644
--- a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/DotNetOpenAuth.OAuth2.AuthorizationServer.csproj
+++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/DotNetOpenAuth.OAuth2.AuthorizationServer.csproj
@@ -18,7 +18,33 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
</PropertyGroup>
<ItemGroup>
+ <Compile Include="Configuration\OAuth2AuthorizationServerSection.cs" />
<Compile Include="OAuth2\AuthorizationServer.cs" />
+ <Compile Include="OAuth2\AuthorizationServerAccessToken.cs" />
+ <Compile Include="OAuth2\AuthServerStrings.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DesignTime>True</DesignTime>
+ <DependentUpon>AuthServerStrings.resx</DependentUpon>
+ </Compile>
+ <Compile Include="OAuth2\AuthServerUtilities.cs" />
+ <Compile Include="OAuth2\ChannelElements\AggregatingClientCredentialReader.cs" />
+ <Compile Include="OAuth2\ChannelElements\ClientCredentialHttpBasicReader.cs" />
+ <Compile Include="OAuth2\ChannelElements\ClientCredentialMessagePartReader.cs" />
+ <Compile Include="OAuth2\ChannelElements\TokenCodeSerializationBindingElement.cs" />
+ <Compile Include="OAuth2\ChannelElements\AuthorizationCode.cs" />
+ <Compile Include="OAuth2\ChannelElements\MessageValidationBindingElement.cs" />
+ <Compile Include="OAuth2\ChannelElements\AuthServerBindingElementBase.cs" />
+ <Compile Include="OAuth2\ChannelElements\IOAuth2ChannelWithAuthorizationServer.cs" />
+ <Compile Include="OAuth2\ChannelElements\OAuth2AuthorizationServerChannel.cs" />
+ <Compile Include="OAuth2\ChannelElements\RefreshToken.cs" />
+ <Compile Include="OAuth2\ChannelElements\ClientAuthenticationModule.cs" />
+ <Compile Include="OAuth2\ClientDescription.cs" />
+ <Compile Include="OAuth2\IAuthorizationServerHost.cs" />
+ <Compile Include="OAuth2\Messages\AccessTokenAuthorizationCodeRequestAS.cs" />
+ <Compile Include="OAuth2\Messages\AccessTokenRefreshRequestAS.cs" />
+ <Compile Include="OAuth2\Messages\EndUserAuthorizationSuccessAuthCodeResponseAS.cs" />
+ <Compile Include="OAuth2\Messages\IAuthorizationCodeCarryingRequest.cs" />
+ <Compile Include="OAuth2\Messages\IRefreshTokenCarryingRequest.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
@@ -26,11 +52,21 @@
<Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project>
<Name>DotNetOpenAuth.Core</Name>
</ProjectReference>
+ <ProjectReference Include="..\DotNetOpenAuth.OAuth2.ClientAuthorization\DotNetOpenAuth.OAuth2.ClientAuthorization.csproj">
+ <Project>{CCF3728A-B3D7-404A-9BC6-75197135F2D7}</Project>
+ <Name>DotNetOpenAuth.OAuth2.ClientAuthorization</Name>
+ </ProjectReference>
<ProjectReference Include="..\DotNetOpenAuth.OAuth2\DotNetOpenAuth.OAuth2.csproj">
<Project>{56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}</Project>
<Name>DotNetOpenAuth.OAuth2</Name>
</ProjectReference>
</ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="OAuth2\AuthServerStrings.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>AuthServerStrings.Designer.cs</LastGenOutput>
+ </EmbeddedResource>
+ </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(ProjectRoot)tools\DotNetOpenAuth.targets" />
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))\EnlistmentInfo.targets" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))' != '' " />
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; }
+ }
+}